Exoplanet Classification Problem

For this project, we decided to perform classification on our data set. Classification is a way to predict a label, in our case categorical, based on the characteristics of the data. The

The objective of our project is to use machine learning classification models to determine if a given observation of a star should be classified as an exoplanet (a planet outside our solar system). The original data set comes from the Kepler Space Observatory, and details 9,564 observations of potential exoplanets, along with 50 descriptive features ranging from identifiers to specific measurements. The column “koi_disposition” labelled each of the potential exoplanets as either “CONFIRMED,” “FALSE POSITIVE,” or “CANDIDATE”. Those labeled “CANDIDATE” have not yet been determined to be exoplanets or not; the goal of our analysis will be to label them as “CONFIRMED” or “FALSE POSITIVE.” NOTE: In this context, “false positive” does not indicate a false positive output from our model. Instead, it refers to the original observation, which was considered a candidate, to have been falsely identified as a candidate.

To solve this problem, we will carry out the following process:

  1. Load, preprocess, and clean up data
  2. Data visualization and exploratory data analysis
  3. Creation and testing of candidate models:
    • 3.1) Models using non-scaled data:
      • 3.1.1) Decision Tree, Random Forest, and Support Vector Machine
      • 3.1.2) XGBoost
      • 3.1.3) Logistic Regression
    • 3.2) Models using scaled data:
      • 3.2.1) Neural Network: Original Variables
      • 3.2.2) Neural Network: PCA w/15 Variables
      • 3.2.3) Neural Network: PCA w/20 Variables
      • 3.2.4) K-Nearest Neighbours
      • 3.2.5) K-Means Clustering
  4. Select best model
  5. Use best model to make predictions

1. Data Loading, Pre-Processing and Clean Up

Load Libraries:

library(Matrix)    #extra Matrix functionality
library(rpart)      #Decision trees
library(rpart.plot)
library(randomForest)#random forest
library(class)      #KNN
library(e1071)      #misc. stats functionary
library(xgboost)    #XGBoost Algorithm
library(FNN)        #KNN
library(factoextra) #PCA and clustering
library(ggplot2)    #extra plotting functionality
library(corrplot)   #extra plotting functionality
#visualization packages
library(dplyr)
library(reshape2)
library(tidyverse)
library(DiagrammeR) #plotting for XGBoost
library(formulaic) #automated formula creation
library(neuralnet) #Neural Networks

The first step was to load the data, and convert our output variable, “koi_disposition”, to a factor

#read data
originalKepplerData = read.csv("cumulative.csv") #read data
factored_keppler_data = originalKepplerData
factored_keppler_data$koi_disposition = factor(originalKepplerData$koi_disposition) #factor character data
factored_keppler_data$koi_pdisposition = factor(originalKepplerData$koi_pdisposition)
head(factored_keppler_data)

Our next step was to conduct forms of dimensionality reduction on our 49 possible input variables. Dimensionality reduction is important for our large data set to reduce the computational requirements of models and reduce possible overfitting or bias from un-important features. First, we removed several entirely empty columns (with values of 0 or NA), unique row identifiers (observation numbers), as well as several which could only be filled once the outcome of the observation was already known. For example, “kepler_name” is the name given to a confirmed exoplanet, so it was removed from the data set. There were no unique outlying data points that had to be explored further.

#remove all-NA columns:
remove_KOI_tech_factored = subset(factored_keppler_data, select = -c(koi_teq_err1)) #remove 
remove_KOI_tech_factored = subset(remove_KOI_tech_factored, select = -c(koi_teq_err2))
remove_NAs = na.exclude(remove_KOI_tech_factored) #remove any rows with NAs remaining

#Remove unique identifiers and rows that can only be known after the status of a planet is known. They therefore are not useful inputs to determine the status of a candidate.
identifiers_removed = subset(
  remove_NAs,
  select = -c(
    rowid,
    kepid,
    kepoi_name,
    kepler_name,
    koi_pdisposition,
    koi_score
  )
)

#Also needs to remove flags, these can only be known after status is confirmed
identifiers_removed = subset(
  identifiers_removed,
  select = -c(
    koi_fpflag_ss,
    koi_fpflag_ec,
    koi_fpflag_co,
    koi_fpflag_nt,
    koi_tce_plnt_num,
    koi_tce_delivname
  )
)

#Now, we will separate in labeled and unlabeled data. The labeled data will be used to train our model; once the best model is selected, we will use it to predict the labels of the unlabeled dataset
candidates_final = identifiers_removed[identifiers_removed$koi_disposition ==
                                         "CANDIDATE",] #separate out just the candidates
labeled_final = identifiers_removed[identifiers_removed$koi_disposition !=
                                      "CANDIDATE",]
labeled_final = droplevels(labeled_final)       
candidates_final = droplevels(candidates_final)

numFalse = sum(labeled_final$koi_disposition=="FALSE POSITIVE")
numConfirmed = sum(labeled_final$koi_disposition=="CONFIRMED")
##check
numFalse + numConfirmed == dim(labeled_final)[1]
[1] TRUE
Proportions = data.frame(label = c("CONFIRMED","FALSE POSITIVE"), number = c(numConfirmed,numFalse))
bp = ggplot(Proportions,aes(x = "",y=number,fill=label))+geom_bar(width = 1,stat = "identity")
pie = bp+coord_polar("y", start = 0) + ggtitle("Proportions of labeled_final that are CONFIRMED or FALSE POSITIVE")
pie

head(labeled_final)
head(candidates_final)

We are left with two datasets:

2) Data Visualization and Exploratory Data Analysis

Select the variables to be used:

#All variables have errors - for exploratory data analysis we will only be looking at the variables themselves, not their errors
variablesonly = labeled_final %>%
  select(koi_period, koi_time0bk, koi_impact, koi_duration, koi_depth, koi_prad, koi_teq, koi_insol, koi_model_snr, koi_steff, koi_slogg, koi_srad, ra, dec, koi_kepmag)

Create a correlation heat map with only the variables (excluding the errors).

cormat <- round(cor(variablesonly),2)
head(cormat)
             koi_period koi_time0bk koi_impact koi_duration koi_depth koi_prad koi_teq koi_insol
koi_period         1.00        0.60      -0.03         0.33     -0.05    -0.01   -0.35     -0.02
koi_time0bk        0.60        1.00       0.02         0.20     -0.05    -0.01   -0.28     -0.02
koi_impact        -0.03        0.02       1.00         0.06      0.02     0.54    0.05     -0.01
koi_duration       0.33        0.20       0.06         1.00      0.09     0.02   -0.19     -0.02
koi_depth         -0.05       -0.05       0.02         0.09      1.00     0.08    0.06     -0.01
koi_prad          -0.01       -0.01       0.54         0.02      0.08     1.00    0.12      0.05
             koi_model_snr koi_steff koi_slogg koi_srad    ra   dec koi_kepmag
koi_period           -0.04      0.01     -0.04     0.01 -0.05  0.02      -0.02
koi_time0bk          -0.04     -0.01      0.02    -0.01 -0.03  0.00       0.03
koi_impact            0.03      0.08     -0.03     0.00  0.07 -0.03       0.02
koi_duration          0.10      0.10     -0.13     0.02  0.04 -0.03      -0.10
koi_depth             0.60      0.15     -0.03    -0.02  0.02 -0.01       0.00
koi_prad              0.05     -0.01     -0.17     0.19  0.03  0.00      -0.01
melted_cormat <- melt(cormat)
head(melted_cormat)

ggplot(data = melted_cormat, aes(x=Var1, y=Var2, fill=value)) + 
  geom_tile()

Defining significant correlations as those greater than .4, there are a few; this will need to be dealt with in Logistic Regression, but should not affect other models.

Plots by FALSE POSITIVE and CONFIRMED

We will now observe the distribution of several variables across false positive and confirmed exoplanets. This will help determine where there is a clear pattern in certain variables.

ggplot(data = labeled_final) +
  geom_point(mapping = aes(x = koi_period, y = koi_time0bk)) + 
  facet_wrap(~ koi_disposition, nrow = 2)

For koi_period and koi_time0bk, the distributions of the variables seem similars, with some outliers. That being said, FALSE POSITIVEs can have significantly higher koi_periods than CONFIRMEDs

ggplot(data = labeled_final) +
  geom_point(mapping = aes(x = koi_impact, y = koi_duration)) + 
  facet_wrap(~ koi_disposition, nrow = 2)

The plot of koi_impact vs. koi_duration shows clear differences between their distributions - CONFIRMEDs have a significantly small range for both variables, although those candidates that fall inside that range may be difficult to classfiy.

ggplot(data = labeled_final) +
  geom_point(mapping = aes(x = koi_depth, y = koi_prad)) + 
  facet_wrap(~ koi_disposition, nrow = 2)

koi_depth shows a clear different: high koi_depths almost always indicate FALSE POSITIVEs

ggplot(data = labeled_final) +
  geom_point(mapping = aes(x = koi_teq, y = koi_insol)) + 
  facet_wrap(~ koi_disposition, nrow = 2)

koi_teq has a significantly smaller range for CONFIRMED data points, and KOI_insol stays closer to zero. If we look at

summary(labeled_final$koi_insol)
    Min.  1st Qu.   Median     Mean  3rd Qu.     Max. 
       0       36      219     8148     1332 10947555 
summary(labeled_final$koi_teq)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
     92     626     981    1192    1540   14667 

This confirms that there are very large values for FALSE POSITIVES that are significantly outside the normal data range. It will be easy to classify these as FALSE POSITIVEs. We can explore these graphs with a smaller range, to exclude the outliers:

ggplot(data = labeled_final[labeled_final$koi_insol<250000,]) +
  geom_point(mapping = aes(x = koi_teq, y = koi_insol)) + 
  facet_wrap(~ koi_disposition, nrow = 2)

This shows that for values of koi_insol < 25000 and koi_teq<3000, it becomes difficult to identify whether a given data point is FALSE POSITIVE or CONFIRMED.

ggplot(data = labeled_final) +
  geom_point(mapping = aes(x = koi_model_snr, y = koi_steff)) + 
  facet_wrap(~ koi_disposition, nrow = 2)

Once again, FALSE POSITIVES show significantly more variability on both axes than CONFIRMEDs

ggplot(data = labeled_final) +
  geom_point(mapping = aes(x = koi_slogg, y = koi_srad)) + 
  facet_wrap(~ koi_disposition, nrow = 2)

FALSE POSITIVEs can have significantly lower koi_sloggs and higher koi_srads than CONFIRMEDs

ggplot(data = labeled_final) +
  geom_point(mapping = aes(x = ra, y = dec)) +
  facet_wrap( ~ koi_disposition, nrow = 2)

Lastly, for ra and dec, there is no clear different in distribution for CONFIRMED or FALSE POSITIVE.

Overall, an easy way to determine FALSE POSITIVEs tends to be to look for data points outside a given range. Inside that range, the determination of whether an observation is a exoplanet or not becomes more difficult.

3. Creation and testing of candidate models

Tto classify the data and determine which observation are, in fact, planets, our group first had to determine which model most accurately predicted our labeled data set. We first examined models that did not require scaling, these included: Decision Tree, Random Forest, XGBoost, Logistic Regression, and Support Vector Machines (SVM). Next, we examined models that required scaling, these included: KNN, Neural Network (with and without Principal Component Analysis), and K-means clustering. To choose the best model for our data set, our group decided to look at which model produced the lowest misclassification rate. However, it should be noted that in real-life application a number of other metrics should be considered as well. These include, but are not limited to, precision analysis (PR curves), recall, ROC-AUC, and F1 Scores.

When running the various models, our group incorporated K-fold cross-validation. The benefit of this approach is that it allows the model to become more generalized, helping with over-fitting concerns. In addition, we take the average result of five misclassification rates for each model, significantly lowering the chance that a given misclassification rate is produced only by chance due to a specific testing/training set. This allows us to be more confident that the model with the lowest misclassification rate truly is the best. Due to computational limits, we decided to use a K value of 5 as we believed that would be adequate for our purposes. Finally, when deciding the proportion of training and testing set, we decided to follow class standards with 80% training set and 20% testing set. Our specific data set is fairly large with ~6,031 rows; we believe having ~4800 rows to train and ~1,200 rows to test is large enough for each purpose.

3.1) Models using non-scaled data:

3.1.1) Decision Tree, Random Forest, and SVM

The goal of a decision tree is to make splitting decisions on the data, in an effort to minimize the least squares, thus creating a tree-like structure. These models are useful as a starting point because they are easy to interpret as the plot can display which variables have the highest importance in the tree. Normalization and other data cleaning is also not required for this model. Adding more depth to the tree can reduce the fitting error to the data, but it can lead to overfitting the model. As a result, the complexity parameter, cp, must be tuned to ensure that the optimal depth-to-fit of the model is used. As the cp value decreases, so does the relative error in the model. We automatically prune our decision tree to select the cp where the change in error is less than 0.05; this is our first example of parameter tuning.

Random forest models are more accurate and robust but harder to interpret than a single tree. The model creates many decision trees with different randomized learning and testing sets, then the trees “vote” or “average” their results to determine the resultant random forest model. Though the model is not as interpretable as a single tree and it is more difficult to understand the significance of a single variable, it will result in lower misclassification rate. The number of trees in the forest is a parameter that needs to be tuned in this model. As the number of trees increases, the error decreases exponentially, reaching an asymptote of error.

SVM models are supervised learning models that use the data points to create a line to separate the data. This separation then decides the binary classification for each data point. While this model can be incredibly versatile and robust against outliers and inaccurate data, it may not be as accurate if there is much overlap between the data.

Helper Function:

AllErrors = function(correctResults,
                     predictedResults,
                     sizeTestSet, numerical = 0) {
  isWrong = (correctResults != predictedResults)
  isRight = (correctResults == predictedResults)
  Errors = sum(correctResults != predictedResults)
  ErrorRate = Errors / sizeTestSet
  
  
  if (numerical == 0) {
    IsC = (predictedResults == 'CONFIRMED')
    IsF = (predictedResults == 'FALSE POSITIVE')
  }else{
    IsC = (predictedResults == 1)
    IsF = (predictedResults == 0)
  }
  
  FalsePositives = sum(isWrong & IsC)
  FalseNegatives = sum(isWrong & IsF)
  TruePositives = sum(isRight & IsC)
  TrueNegatives = sum(isRight & IsF)
  FP_Rate = (FalsePositives / (FalsePositives + TrueNegatives))#define FP rate as FP/(FP+TN)
  FN_Rate = (FalseNegatives / (FalseNegatives + TruePositives))#define FN rate as FN/(FN+TP)
  return(list(ErrorRate, FP_Rate, FN_Rate))
}

Code for decision tree, random forest, and SVM:



NumFolds = 5
CreateErrorMatrix = function(numFolds) {
  return(rep(0, numFolds))
}


decTree_Error = CreateErrorMatrix(NumFolds)
decTree_FP = CreateErrorMatrix(NumFolds)
decTree_FN = CreateErrorMatrix(NumFolds)

RF_error = CreateErrorMatrix(NumFolds)
RF_error_FP = CreateErrorMatrix(NumFolds)
RF_error_FN = CreateErrorMatrix(NumFolds)
SVM_error = CreateErrorMatrix(NumFolds)
SVM_error_FP = CreateErrorMatrix(NumFolds)
SVM_error_FN = CreateErrorMatrix(NumFolds)
for (fold in 1:NumFolds) {
  #set up k-crossfold validation
  set.seed(fold)
  
  
  #split data into testing and training sets
  num_samples = dim(labeled_final)[1]
  sampling.rate = 0.8
  training = sample(1:num_samples, sampling.rate * num_samples, replace = FALSE)
  trainingSet = subset(labeled_final[training, ])
  testing = setdiff(1:num_samples, training)
  testingSet = subset(labeled_final[testing,])
  
  
  #Decision tree
  decTreeModel = rpart(koi_disposition ~ ., data = trainingSet)
  
  #Automatically select the stopping point where cp no longer improves error by 0.05
  errors = decTreeModel$cptable[, 3]
  decTreeChangeError = c(0, 0, 0, 0, 0, 0, 0, 0, 0)
  for (i in 1:8) {
    decTreeChangeError[i] = errors[i + 1] - errors[i]
  }
  decTreeChangeError
  for (i in 1:9) {
    if (abs(decTreeChangeError[i]) < 0.05) {
      stopIndex = i
      break
    }
  }
  cps = decTreeModel$cptable[, 1]
  cpStop = cps[stopIndex]
  
  
  prunedDecTreeModel = rpart::prune(decTreeModel, cp = cpStop) #Prune decision tree
  decTreePredictions = predict(prunedDecTreeModel, testingSet, type = "class") #make predictions
  #Determine decision tree error
  sizeTestSet = dim(testingSet)[1]
  decTreeModel_errors = AllErrors(testingSet$koi_disposition,
                                  decTreePredictions,
                                  sizeTestSet)
  decTree_Error[fold] = decTreeModel_errors[[1]]
  decTree_FP[fold] = decTreeModel_errors[[2]]
  decTree_FN[fold] = decTreeModel_errors[[3]]
  #Random Forest
  RandForestModel = randomForest(koi_disposition ~ ., data = trainingSet, ntrees = 200)#visual inspection gives this as a good number for trees
  
  predictedLabels = predict(RandForestModel, testingSet)
  
  #Determine Random Forest Error
  sizeTestSet = dim(testingSet)[1]
  RF_Errors = AllErrors(testingSet$koi_disposition, predictedLabels, sizeTestSet)
  

  RF_error[fold] = RF_Errors[[1]]
  RF_error_FP[fold] = RF_Errors[[2]]
  RF_error_FN[fold] = RF_Errors[[3]]
  
  #SVM Model
  svmModel = svm(koi_disposition ~ ., data = trainingSet, kernel = "linear")
  predictedlabelsSVM = predict(svmModel, testingSet)
  #Determine SVM error
  SVM_Errors = AllErrors(testingSet$koi_disposition,
                         predictedlabelsSVM,
                         sizeTestSet)
  
  errorSVM = sum(predictedlabelsSVM != testingSet$koi_disposition)
  misclassification_rateSVM = errorSVM / sizeTestSet
  SVM_error[fold] = SVM_Errors[[1]]
  SVM_error_FP[fold] = SVM_Errors[[2]]
  SVM_error_FN[fold] = SVM_Errors[[3]]
}
#Take average of errors from each fold to determine average error for each model

AvgErrorDT = mean(decTree_Error)
AvgFP_DT = mean(decTree_FP)
AvgFN_DT = mean(decTree_FN)
AvgErrorRF = mean(RF_error)
AvgFP_RF = mean(RF_error_FP)
AvgFN_RF = mean(RF_error_FN)
AvgErrorSVM = mean(SVM_error)
AvgFP_SVM = mean(SVM_error_FP)
AvgFN_SVM = mean(SVM_error_FN)

paste("DT Error: ", round(100 * AvgErrorDT, 2), "%", sep = "")
[1] "DT Error: 11.58%"
paste("RF Error: ", round(100 * AvgErrorRF, 2), "%", sep = "")
[1] "RF Error: 6.74%"
paste("SVM Error: ", round(100 * AvgErrorSVM, 2), "%", sep = "")
[1] "SVM Error: 8.7%"

All three models give low misclassification rates <12%, but Random Forest gives the best misclassification rate at only 6.74%.

3.1.2) XGBoost

XGBoost stands for “extreme gradient boosting” and is an open-source tree learning algorithm similar to random forests that is also widely used in industry. This model seeks to minimize an objective function representing model complexity and loss (error), using a gradient descent algorithm to minimize loss when adding new models. This is known as tree boosting; random forest models differ because they use a tree bagging algorithm, possibly leading to different model accuracies.

XGBoost outputs a probability between 0 and 1, rather than a binary classification. For this reason, it is necessary to determine the “threshold” where a value stops being a FALSE POSITIVE, and start being a CONFIRMED. Questioning the classification threshold which is built into our models can help us develop more accurate models. For example, arbitrarily assuming that the threshold for classifying based on our XGBoost model was exactly 0.5 could have resulted in a higher misclassification rate if we were to consider all possible thresholds. As a result, we conducted threshold analysis on all our models that output probabilities by looping through all classification thresholds at 0.1 increments, plotted them against their respective misclassification rates, and took the minimum as the optimal. The first model that we have done this for is XGBoost.

xgb.set.config(verbosity = 0)
[1] TRUE
threshold = 0.1
XGBoost_error_thresholds = rep(0, 10)
XGBoost_FP_thresholds = rep(0,10)
XGBoost_FN_thresholds = rep(0,10)
index = 1

while (threshold < 1) {
  #loop that reruns the algorithm with increments of 0.1 in the threshold
  XGB_error = CreateErrorMatrix(NumFolds)
  XGB_FNs = CreateErrorMatrix(NumFolds)
  XGB_FPs = CreateErrorMatrix(NumFolds)
  for (fold in 1:NumFolds) {
    #k cross-fold validation
    set.seed(fold)
    
    num_samples = dim(labeled_final)[1]
    
    #create testing and training set
    sampling.rate = 0.8
    training = sample(1:num_samples, sampling.rate * num_samples, replace = FALSE)
    trainingSet = subset(labeled_final[training,])
    testing = setdiff(1:num_samples, training)
    testingSet = subset(labeled_final[testing, ])
    
    #XGBoost model
    xgTrain = data.matrix(trainingSet)
    xgTrain[, 1] = ifelse(xgTrain[, 1] == 2, 1, 0)
   
    xgBoostModel = xgboost(
      data = xgTrain[, 2:36],
      label = xgTrain[, 1],
      max.depth = 6,
      eta = .22,
      nrounds = 100,
      verbose = 0,
      objective = "binary:logistic",
      eval_metric="error"
    )
    xgTest = data.matrix(testingSet)
    xgTest[, 1] = ifelse(xgTest[, 1] == 2, 1, 0)
    
    
    
    #make predictions
    BoostPredictions = predict(xgBoostModel, data.matrix(testingSet)[, 2:36])
    
    
    
    
    BoostPredictionsRounded = ifelse(BoostPredictions > threshold, 1, 0) #convert probabilities to ouputs of 1 or 0 based on whether they are greater than the threshold - this is where parameter tuning occurs.
    BoostErrors = AllErrors(xgTest[, 1],BoostPredictionsRounded,dim(xgTest)[1],1)
    XGB_error[fold] = BoostErrors[[1]]
    XGB_FPs = BoostErrors[[2]]
    XGB_FNs = BoostErrors[[3]]
   
  }
  XGBoost_error_thresholds[index] = mean(XGB_error)#average the error from all folds
  XGBoost_FP_thresholds[index] = mean(XGB_FPs)
  XGBoost_FN_thresholds[index] = mean(XGB_FNs)
  
  index = index + 1
  threshold = threshold + .1
}
xgbPlot = xgb.plot.tree(model = xgBoostModel,
                        trees = 1,
                        render = TRUE)
xgbPlot #plot an example tree


#Determine correct threshold value
XGBoost_error_thresholds#shows the average error at each threshold
 [1] 0.07854184 0.06611433 0.06197183 0.06081193 0.06081193 0.06362883 0.06545153 0.06992543 0.07887324
[10] 0.62220381
order(XGBoost_error_thresholds) #Lowest error is threshold = .4
 [1]  4  5  3  6  7  2  8  1  9 10
AvgErrorXGB = XGBoost_error_thresholds[(order(XGBoost_error_thresholds)[1])] #chose the threshold with the lowest error as the one we use
AvgFP_XGB = XGBoost_FP_thresholds[(order(XGBoost_error_thresholds)[1])]
AvgFN_XGB = XGBoost_FN_thresholds[(order(XGBoost_error_thresholds)[1])]
paste("XGBoost Error: ", round(AvgErrorXGB*100,2), "%", sep = "")
[1] "XGBoost Error: 6.08%"
#plot the error thresholds
plot(
  x = 1:10 / 10,
  y = XGBoost_error_thresholds,
  main = "Avg Error over different thresholds for XGBoost Model",
  xlab = "Cutoff Threshold",
  ylab = "Misclassification Rate"
)
lines(x = 1:10 / 10, y = XGBoost_error_thresholds)

XGBoost gives an error rate of 6.08%. This is significantly better than any model run so far. The threshold analysis graph above shows that the best misclassification rate for XGBoost is at the threshold = .4.

3.1.5) Logistic Regression

Logistic regression models is a supervised classification algorithm that builds a regression model to predict the classification by assigning data entries to binary values, based on the Sigmoid function. When performing logistic regression, it is important to consider the problems that arise from multicollinearity which can cause unstable estimates and inaccuracy. For this reason, we decided to first remove all major multicollinearity from the model.

#Check for multicollinearity
corrFrame = data.frame(cor(labeled_final[, 2:36]))
corrplot(cor(labeled_final[, 2:36])) #many multicollinear variables. Definine collinearity as correlation >.4



GLM_Data = data.frame(labeled_final$koi_disposition)


#all err2s are collinear with err1s. Removing err1s
variable_counter = 2
variable.names = colnames(labeled_final)
for (i in 2:length(labeled_final)) {
  variable.names[i]
  error1 = grepl("_err1", variable.names[i], fixed = TRUE)
  if (error1 == FALSE) {
    GLM_Data[, variable_counter] = labeled_final[, i]
    variable_counter = variable_counter + 1
  } else{
    variable.names[i] = NA
  }
}
variable.names = na.omit(variable.names)
colnames(GLM_Data) = variable.names
GLM_Data

corrFrame2 = data.frame(cor(GLM_Data[, 2:dim(GLM_Data)[2]]))
corrplot(cor(GLM_Data[, 2:dim(GLM_Data)[2]])) #Many correlated variables remain



#KOI_period is collinear with koi_time0b
GLM_Data = subset(GLM_Data, select = -c(koi_time0bk))
#koi_period_err is collinear with koi_time0bk error
GLM_Data = subset(GLM_Data, select = -c(koi_time0bk_err2))
#Koi_period is collinear with koi_period_err2
GLM_Data = subset(GLM_Data, select = -c(koi_period_err2))
#koi_impact is collinear with koi_impact_err2
GLM_Data = subset(GLM_Data, select = -c(koi_impact_err2))
#koi_depth is collinear with koi_model_snr
GLM_Data = subset(GLM_Data, select = -c(koi_model_snr))
#koi impact is collinear with koi_prad and koi_prad_err
GLM_Data = subset(GLM_Data, select = -c(koi_prad, koi_prad_err2))
#koi_teq is collinear with KOI_insol, Koi_insol_err, koi_slogg, koi_srad, and koi_srad_err
GLM_Data = subset(GLM_Data,
                  select = -c(koi_insol, koi_insol_err2, koi_slogg, koi_srad, koi_srad_err2))
#koi_steff is collinear with koi_steff_err2 and koi_slogg_err2
GLM_Data = subset(GLM_Data, select = -c(koi_slogg_err2, koi_steff_err2))
corrplot(cor(GLM_Data[, 2:dim(GLM_Data)[2]]))

#All major multicollinearity has now been removed

Similarly to XGBoost, the cutoff threshold for prediction must be tuned. Our group decided to run multiple tests ranging from 0.1 to 1 to determine that the ideal threshold value of 0.5 should be used as that corresponded with the lowest average error.

threshold = 0.1
GLM_error = CreateErrorMatrix(NumFolds)
GLM_error_thresholds = rep(0, 10)
GLM_FP_thresholds = rep(0, 10)
GLM_FN_thresholds = rep(0, 10)
index = 1
while (threshold < 1) {
  #loop for threshold analysis
  GLM_error = CreateErrorMatrix(NumFolds)
  GLM_FP = CreateErrorMatrix(NumFolds)
  GLM_FN = CreateErrorMatrix(NumFolds)
  for (fold in 1:5) {
    #K-cross fold validation
    
    #training/testing set
    set.seed(fold)
    num_samples = dim(GLM_Data)[1]
    sampling.rate = 0.8
    training = sample(1:num_samples, sampling.rate * num_samples, replace = FALSE)
    trainingSet = subset(GLM_Data[training,])
    testing = setdiff(1:num_samples, training)
    testingSet = subset(GLM_Data[testing, ])
    defaultW = getOption("warn")
    options(warn = -1)
    #set up model
    LogisticReg = glm(koi_disposition ~ .,
                      data = trainingSet,
                      family = binomial(logit))
    
    
    options(warn = defaultW)
    predictions = predict(LogisticReg, testingSet, type = "response")
    predictedLabels = rep(0, sizeTestSet)
    predictedLabels = ifelse(predictions > threshold, 'FALSE POSITIVE', 'CONFIRMED') #this parameter is tuned
    
    GLMErrors = AllErrors(testingSet$koi_disposition, predictedLabels, sizeTestSet, 0)
    #determine error
    # error = sum(predictedLabels != testingSet$koi_disposition)
    #misclassificationRateLR = error / sizeTestSet
    #GLM_error[fold] = misclassificationRateLR
    GLM_error[fold] = GLMErrors[[1]]
    GLM_FP[fold] = GLMErrors[[2]]
    GLM_FN[fold] = GLMErrors[[3]]
    
  }
  
  GLM_error_thresholds[index] = mean(GLM_error)
  GLM_FP_thresholds[index] = mean(GLM_FP)
  GLM_FN_thresholds[index] = mean(GLM_FN)
  index = index + 1
  threshold = threshold + .1
}
order(GLM_error_thresholds) #Lowest error is threshold = .5
 [1]  5  4  6  3  7  2  8  9  1 10
AvgErrorGLM = GLM_error_thresholds[order(GLM_error_thresholds)[1]]
AvgFP_GLM = GLM_FP_thresholds[order(GLM_error_thresholds)[1]]
AvgFN_GLM = GLM_FN_thresholds[order(GLM_error_thresholds)[1]]

paste("Logistic Regression Error: ", round(AvgErrorGLM*100,2),"%",sep="")
[1] "Logistic Regression Error: 11.73%"
plot(
  x = 1:10 / 10,
  y = GLM_error_thresholds,
  main = "Avg Error over different thresholds for Logistic Regression Model",
  xlab = "Cutoff Threshold",
  ylab = "Misclassification Rate"
)
lines(x = 1:10 / 10, y = GLM_error_thresholds)

Logistic Regression has an error rate of 11.7%, making it the worst model yet. Its error is best when threshold = .5.

3.2) Models using scaled data:

The models below all require normalization of the data to be effective. This is an important step as all features need to be in the same scale. If not, the features with larger scales would dominate the model causing it to be inaccurate. To do this, we used the “scale” function to normalize all the dependent variables. We also changed the independent variable, koi_disposition, to be binary

3.2.2) Neural Network: Original Variables

The Neural Network model is built through functions, “neurons” that are then organized into layers. It is an advanced model that is ideally suited for complex problems as it requires significant computational resources. In addition, it is quite difficult to understand afterwards given the complexity of the math within the model. Our group was able to see the significant use of computational resources as our computer was unable to run the model. For this reason, the group did not use K-fold cross-validation in order to lower the computational power required to run the model, but in real-life application K-fold cross-validation should still be done.

When choosing the number of neurons and hidden layers it is important to find the right balance between accuracy and over-fitting. Our group manually adjusted the number of neurons and hidden layers, testing variations such as 4&2, 5&1, 6, 3&1, etc, until we found that two neurons and one hidden layer allowed the model to converge. The next step in the model was to choose the threshold value for classification. This was similar to choosing the threshold as we did in Logistic Regression. The ideal threshold of 0.5 was found by plotting the average error for each threshold ranging from 0.1 to 1 as that corresponded with the lowest misclassification rate.

#Create testing and training set
set.seed(123)
scaled_data = data.frame(scale(labeled_final[, 2:36])) #normalize data
scaled_data$koi_disposition = ifelse(labeled_final$koi_disposition == "CONFIRMED", 1, 0)
num_samples = dim(scaled_data)[1]
sampling.rate = 0.8
training = sample(1:num_samples, sampling.rate * num_samples, replace = FALSE)
trainingSet.norm = subset(scaled_data[training,])
testing = setdiff(1:num_samples, training)
testingSet.norm = subset(scaled_data[testing, ])
sizeTestSet = dim(testingSet.norm)[1]

#set up variables for neural network. Since 36 variables put too much computational strain, dimensionality reduction needed to take place. We chose to take only the features, not their errors, to reduce dimensionality.

koiDP.name = "koi_disposition"
numCols = dim(testingSet.norm)[2]
variable.names = colnames(testingSet.norm)[1:numCols - 1]
variable.names
 [1] "koi_period"        "koi_period_err1"   "koi_period_err2"   "koi_time0bk"       "koi_time0bk_err1" 
 [6] "koi_time0bk_err2"  "koi_impact"        "koi_impact_err1"   "koi_impact_err2"   "koi_duration"     
[11] "koi_duration_err1" "koi_duration_err2" "koi_depth"         "koi_depth_err1"    "koi_depth_err2"   
[16] "koi_prad"          "koi_prad_err1"     "koi_prad_err2"     "koi_teq"           "koi_insol"        
[21] "koi_insol_err1"    "koi_insol_err2"    "koi_model_snr"     "koi_steff"         "koi_steff_err1"   
[26] "koi_steff_err2"    "koi_slogg"         "koi_slogg_err1"    "koi_slogg_err2"    "koi_srad"         
[31] "koi_srad_err1"     "koi_srad_err2"     "ra"                "dec"               "koi_kepmag"       
for (i in 1:length(variable.names)) {
  error = grepl("_err", variable.names[i], fixed = TRUE)
  if (error) {
    variable.names[i] = NA
  }
  
}
variable.names = na.omit(variable.names)
variable.names = variable.names[1:15]
#use formulaic library to create formula
nn.form <-
  create.formula(outcome.name = koiDP.name,
                 input.names = variable.names)
nn.form #15 variables
$formula
koi_disposition ~ koi_period + koi_time0bk + koi_impact + koi_duration + 
    koi_depth + koi_prad + koi_teq + koi_insol + koi_model_snr + 
    koi_steff + koi_slogg + koi_srad + ra + dec + koi_kepmag
<environment: 0x000002b15aa84478>

$inclusion.table

$interactions.table
#Fit neural network
nnModel1 = neuralnet(
  nn.form,
  data = trainingSet.norm,
  hidden = 2,
  linear.output = FALSE,
  act.fct = "logistic",
)
plot(nnModel1)


#Predict
predictedLabels = compute(nnModel1, testingSet.norm[, variable.names])

#tune threshold parameter
threshold = .1
NNErrors = CreateErrorMatrix(10)
NN_FPs = CreateErrorMatrix(10)
NN_FNs = CreateErrorMatrix(10)
index = 1
while (threshold <= 1) {
  results = data.frame(actual = testingSet.norm$koi_disposition,
                       prediction = predictedLabels$net.result)
  results$roundedPrediction = ifelse(results$prediction > threshold, 1, 0)
  #error = sum(results$actual != results$roundedPrediction)
  Errors = AllErrors(results$actual,results$roundedPrediction,sizeTestSet,1)
  NNErrors[index] = Errors[[1]]
  NN_FPs[index] = Errors[[2]]
  NN_FNs[index] = Errors[[3]]
  threshold = threshold + .1
  index = index + 1
}

order(NNErrors) #lowest misclass rate is at threshold = .5
 [1]  5  4  6  3  7  2  8  1  9 10
NeuralNetMisClassRate = NNErrors[order(NNErrors)[1]]
AvgFP_NN = NN_FPs[order(NNErrors)[1]]
AvgFN_NN = NN_FNs[order(NNErrors)[1]]
paste("Neural Net Misclassification Rate: ",round(100*NeuralNetMisClassRate,2),"%",sep="")
[1] "Neural Net Misclassification Rate: 10.02%"
plot(
  x = 1:10 / 10,
  y = NNErrors,
  main = "Avg Error over different thresholds for Neural Network",
  xlab = "Cutoff Threshold",
  ylab = "Misclassification Rate"
)
lines(x = 1:10 / 10, y = NNErrors)

In this run, NN had an error rate of 0.1002486, with an optimal cutoff threshold of .5. We will revisit the NN below, to look at ways to use PCA to capture variation while reducing necessary computational resources.

Neural Network: PCA w/15 Variables

Principal Component Analysis allows us to reduce dimensionality while capturing as much of the underlying variation in the data as possible. The first application of PCA we found was for Neural Networks, which are very computationally difficult. We decided to first use a PCA with 15 variables, the same number of variables as our original neural network. This would mean the same computational strain, but with features that are guaranteed to capture as much variation as possible with that number of variables.

res.pca.exoplanets = prcomp(identifiers_removed[2:36], center = TRUE, scale = TRUE) #perform PCA
summary(res.pca.exoplanets)
Importance of components:
                          PC1    PC2     PC3     PC4     PC5     PC6     PC7     PC8     PC9    PC10
Standard deviation     2.2712 2.1730 1.86778 1.72894 1.47639 1.37931 1.33622 1.25447 1.10767 1.04188
Proportion of Variance 0.1474 0.1349 0.09967 0.08541 0.06228 0.05436 0.05101 0.04496 0.03506 0.03101
Cumulative Proportion  0.1474 0.2823 0.38196 0.46737 0.52965 0.58400 0.63502 0.67998 0.71503 0.74605
                          PC11    PC12    PC13    PC14    PC15    PC16    PC17    PC18    PC19    PC20
Standard deviation     1.01171 0.98148 0.95840 0.93448 0.88263 0.78194 0.77439 0.75012 0.67294 0.63205
Proportion of Variance 0.02924 0.02752 0.02624 0.02495 0.02226 0.01747 0.01713 0.01608 0.01294 0.01141
Cumulative Proportion  0.77529 0.80282 0.82906 0.85401 0.87627 0.89374 0.91087 0.92695 0.93989 0.95130
                          PC21    PC22    PC23    PC24    PC25    PC26    PC27    PC28    PC29    PC30
Standard deviation     0.58189 0.55938 0.54423 0.48547 0.44729 0.37145 0.31152 0.23244 0.12420 0.10799
Proportion of Variance 0.00967 0.00894 0.00846 0.00673 0.00572 0.00394 0.00277 0.00154 0.00044 0.00033
Cumulative Proportion  0.96097 0.96991 0.97838 0.98511 0.99083 0.99477 0.99754 0.99909 0.99953 0.99986
                          PC31      PC32      PC33      PC34      PC35
Standard deviation     0.07019 2.036e-15 5.773e-16 5.354e-16 3.988e-16
Proportion of Variance 0.00014 0.000e+00 0.000e+00 0.000e+00 0.000e+00
Cumulative Proportion  1.00000 1.000e+00 1.000e+00 1.000e+00 1.000e+00
PoV <-
  res.pca.exoplanets$sdev ^ 2 / sum(res.pca.exoplanets$sdev ^ 2) #get proportions of variance
numPcas = 15
sum(PoV[1:numPcas]) #This gives us 88% explanation of variance. This is the number of variable in the original neural network, so we are using this to get a better NN with the same number of variables
[1] 0.876268
newDataSet = data.frame(res.pca.exoplanets$x[, 1:numPcas])
newDataSet$label = identifiers_removed$koi_disposition
fviz_pca_var(res.pca.exoplanets, col.var = "contrib")

candidates_PCA = newDataSet[identifiers_removed$koi_disposition ==
                              "CANDIDATE", ] #separate out just the candidates
labeled_PCA = newDataSet[identifiers_removed$koi_disposition !=
                           "CANDIDATE", ]
labeled_PCA = droplevels(labeled_PCA)
candidates_PCA = droplevels(candidates_PCA)

Rerunning Neural Network Model

set.seed(123)
NN_labeled_PCA = labeled_PCA[, 1:numPcas]
NN_labeled_PCA$koi_disposition = ifelse(labeled_PCA$label == "CONFIRMED", 1, 0)
summary(scaled_data)
   koi_period      koi_period_err1   koi_period_err2     koi_time0bk       koi_time0bk_err1  
 Min.   :-0.4049   Min.   :-0.1768   Min.   :-26.7574   Min.   :-0.64352   Min.   :-0.36586  
 1st Qu.:-0.3844   1st Qu.:-0.1762   1st Qu.:  0.1590   1st Qu.:-0.43439   1st Qu.:-0.32745  
 Median :-0.3277   Median :-0.1736   Median :  0.1736   Median :-0.37877   Median :-0.22068  
 Mean   : 0.0000   Mean   : 0.0000   Mean   :  0.0000   Mean   : 0.00000   Mean   : 0.00000  
 3rd Qu.:-0.1552   3rd Qu.:-0.1590   3rd Qu.:  0.1762   3rd Qu.: 0.07978   3rd Qu.:-0.00669  
 Max.   :11.9778   Max.   :26.7574   Max.   :  0.1768   Max.   :23.11291   Max.   :31.75989  
 koi_time0bk_err2      koi_impact       koi_impact_err1   koi_impact_err2     koi_duration     
 Min.   :-31.75989   Min.   :-0.84878   Min.   :-0.2207   Min.   :-37.0338   Min.   :-0.79008  
 1st Qu.:  0.00669   1st Qu.:-0.52855   1st Qu.:-0.2173   1st Qu.: -0.3289   1st Qu.:-0.45677  
 Median :  0.22068   Median :-0.03305   Median :-0.2034   Median :  0.2248   Median :-0.26098  
 Mean   :  0.00000   Mean   : 0.00000   Mean   : 0.0000   Mean   :  0.0000   Mean   : 0.00000  
 3rd Qu.:  0.32745   3rd Qu.: 0.37679   3rd Qu.:-0.1824   3rd Qu.:  0.4915   3rd Qu.: 0.07348  
 Max.   :  0.36586   Max.   :32.39182   Max.   : 8.2794   Max.   :  0.5591   Max.   :19.80016  
 koi_duration_err1  koi_duration_err2     koi_depth       koi_depth_err1     koi_depth_err2     
 Min.   :-0.46838   Min.   :-16.23186   Min.   :-0.3491   Min.   :-0.25725   Min.   :-49.18710  
 1st Qu.:-0.40374   1st Qu.:  0.02716   1st Qu.:-0.3471   1st Qu.:-0.22666   1st Qu.:  0.09117  
 Median :-0.28282   Median :  0.28282   Median :-0.3432   Median :-0.19211   Median :  0.19211  
 Mean   : 0.00000   Mean   :  0.00000   Mean   : 0.0000   Mean   : 0.00000   Mean   :  0.00000  
 3rd Qu.:-0.02716   3rd Qu.:  0.40374   3rd Qu.:-0.3030   3rd Qu.:-0.09117   3rd Qu.:  0.22666  
 Max.   :16.23186   Max.   :  0.46838   Max.   : 9.5013   Max.   :49.18710   Max.   :  0.25725  
    koi_prad        koi_prad_err1      koi_prad_err2          koi_teq          koi_insol       
 Min.   :-0.09248   Min.   :-0.06598   Min.   :-76.00748   Min.   :-1.2681   Min.   :-0.04894  
 1st Qu.:-0.08857   1st Qu.:-0.06405   1st Qu.:  0.03814   1st Qu.:-0.6527   1st Qu.:-0.04872  
 Median :-0.08497   Median :-0.06156   Median :  0.05676   Median :-0.2437   Median :-0.04762  
 Mean   : 0.00000   Mean   : 0.00000   Mean   :  0.00000   Mean   : 0.0000   Mean   : 0.00000  
 3rd Qu.:-0.01473   3rd Qu.:-0.02127   3rd Qu.:  0.05834   3rd Qu.: 0.4011   3rd Qu.:-0.04094  
 Max.   :73.22898   Max.   :75.98152   Max.   :  0.05933   Max.   :15.5272   Max.   :65.69789  
 koi_insol_err1     koi_insol_err2      koi_model_snr       koi_steff        koi_steff_err1   
 Min.   :-0.06739   Min.   :-68.06196   Min.   :-0.3877   Min.   :-3.71885   Min.   :-3.0066  
 1st Qu.:-0.06706   1st Qu.:  0.04559   1st Qu.:-0.3714   1st Qu.:-0.48298   1st Qu.:-0.8168  
 Median :-0.06516   Median :  0.05053   Median :-0.3514   Median : 0.06731   Median : 0.2676  
 Mean   : 0.00000   Mean   :  0.00000   Mean   : 0.0000   Mean   : 0.00000   Mean   : 0.0000  
 3rd Qu.:-0.05080   3rd Qu.:  0.05123   3rd Qu.:-0.2215   3rd Qu.: 0.49193   3rd Qu.: 0.6430  
 Max.   :69.45869   Max.   :  0.05136   Max.   : 9.1486   Max.   :12.43001   Max.   :11.0913  
 koi_steff_err2        koi_slogg       koi_slogg_err1    koi_slogg_err2       koi_srad       
 Min.   :-20.71031   Min.   :-9.8852   Min.   :-0.9095   Min.   :-9.1764   Min.   :-0.28040  
 1st Qu.: -0.45354   1st Qu.:-0.2227   1st Qu.:-0.5830   1st Qu.:-0.8374   1st Qu.:-0.15546  
 Median :  0.03832   Median : 0.2918   Median :-0.3753   Median : 0.1305   Median :-0.12554  
 Mean   :  0.00000   Mean   : 0.0000   Mean   : 0.0000   Mean   : 0.0000   Mean   : 0.00000  
 3rd Qu.:  0.65961   3rd Qu.: 0.5374   3rd Qu.: 0.2109   3rd Qu.: 0.6666   3rd Qu.:-0.06386  
 Max.   :  2.09635   Max.   : 2.2474   Max.   :10.0123   Max.   : 1.9621   Max.   :31.37715  
 koi_srad_err1      koi_srad_err2             ra                dec            koi_kepmag     
 Min.   :-0.35977   Min.   :-56.21894   Min.   :-2.58287   Min.   :-2.0123   Min.   :-5.4016  
 1st Qu.:-0.22791   1st Qu.:  0.07577   1st Qu.:-0.69660   1st Qu.:-0.8514   1st Qu.:-0.5886  
 Median :-0.10044   Median :  0.14915   Median : 0.04099   Median :-0.0363   Median : 0.1868  
 Mean   : 0.00000   Mean   :  0.00000   Mean   : 0.00000   Mean   : 0.0000   Mean   : 0.0000  
 3rd Qu.: 0.00834   3rd Qu.:  0.17252   3rd Qu.: 0.80352   3rd Qu.: 0.8106   3rd Qu.: 0.7616  
 Max.   :36.00187   Max.   :  0.21002   Max.   : 2.00621   Max.   : 2.3631   Max.   : 3.5325  
 koi_disposition 
 Min.   :0.0000  
 1st Qu.:0.0000  
 Median :0.0000  
 Mean   :0.3761  
 3rd Qu.:1.0000  
 Max.   :1.0000  
head(scaled_data)
num_samples = dim(scaled_data)[1]
sampling.rate = 0.8

#create training/testing sets
training = sample(1:num_samples, sampling.rate * num_samples, replace = FALSE)
trainingSet.norm.PCA = subset(NN_labeled_PCA[training,])
testing = setdiff(1:num_samples, training)
testingSet.norm.PCA = subset(NN_labeled_PCA[testing, ])
sizeTestSet = dim(testingSet.norm)[1]
label.name = "label"
variable.names = rep(0, numPcas)
numCols = dim(testingSet.norm.PCA)[2]
variable.names = colnames(testingSet.norm.PCA)[1:numCols - 1]
variable.names
 [1] "PC1"  "PC2"  "PC3"  "PC4"  "PC5"  "PC6"  "PC7"  "PC8"  "PC9"  "PC10" "PC11" "PC12" "PC13" "PC14"
[15] "PC15"
nn.form <-
  create.formula(outcome.name = koiDP.name,
                 input.names = variable.names)
nn.form
$formula
koi_disposition ~ PC1 + PC2 + PC3 + PC4 + PC5 + PC6 + PC7 + PC8 + 
    PC9 + PC10 + PC11 + PC12 + PC13 + PC14 + PC15
<environment: 0x000002b15782a288>

$inclusion.table

$interactions.table
#rerun neural network
nnModel2 = neuralnet(
  nn.form,
  data = trainingSet.norm.PCA,
  hidden = 2,
  linear.output = FALSE,
  act.fct = "logistic"
)
plot(nnModel2)

#Find results
predictedLabels = compute(nnModel2, testingSet.norm.PCA[, variable.names])

#Tune threshold
threshold = .1
NNErrors = rep(0, 10)
NN_FNs = rep(0,10)
NN_FPs = rep(0,10)
index = 1
while (threshold <= 1) {
  results = data.frame(actual = testingSet.norm.PCA$koi_disposition,
                       prediction = predictedLabels$net.result)
  results$roundedPrediction = ifelse(results$prediction > threshold, 1, 0)
  error = sum(results$actual != results$roundedPrediction)
  Errors = AllErrors(results$actual,results$roundedPrediction,sizeTestSet,1)
  NNErrors[index] = Errors[[1]]
  NN_FPs[index] = Errors[[2]]
  NN_FNs[index] = Errors[[3]]
 
  threshold = threshold + .1
  index = index + 1
}
order(NNErrors) #lowest misclassification rate is at threshold = .4
 [1]  4  6  5  3  7  2  8  1  9 10
PCA_15_v_NeuralNetMisClassRate = NNErrors[order(NNErrors)[1]]
Avg_FP_PCA15NN = NN_FPs[order(NNErrors)[1]]
Avg_FN_PCA15NN = NN_FNs[order(NNErrors)[1]]
paste("Neural Net (PCA, 15 var) Misclassification Rate: ",round(100*PCA_15_v_NeuralNetMisClassRate,2),"%",sep="")
[1] "Neural Net (PCA, 15 var) Misclassification Rate: 8.53%"
plot(
  x = 1:10 / 10,
  y = NNErrors,
  main = "Avg Error over thresholds for PCA Neural Network w/13 Vars",
  xlab = "Cutoff Threshold",
  ylab = "Misclassification Rate"
)
lines(x = 1:10 / 10, y = NNErrors)

We get abetter misclassification rate from the PCA version of NN using 13 variables of 8.53%, which is 1.49% less than our last Neural Network, and an optimal cutoff threshold of .5.

3.2.3) Neural Network: PCA w/20 Variables

Lastly (for NNs), we decided to see how many variables could capture 95% of variation. We found that 20 variables was sufficient; this means that 16 of our 36 variables, or 44.44% represent only 5% of variation.

#Last Neural Network, PCA, with 95% of variation explained
numVars = (dim((labeled_final))[2])
for (i in 1:(numVars)) {
  if (sum(PoV[1:i]) >= .95) {
    numPcas = i
    break
    
  }
}

sum(PoV[1:numPcas])
[1] 0.9513002
newDataSet = data.frame(res.pca.exoplanets$x[, 1:numPcas])

newDataSet$label = identifiers_removed$koi_disposition

candidates_PCA = newDataSet[identifiers_removed$koi_disposition ==
                              "CANDIDATE",] #separate out just the candidates
labeled_PCA = newDataSet[identifiers_removed$koi_disposition !=
                           "CANDIDATE",]
labeled_PCA = droplevels(labeled_PCA)
candidates_PCA = droplevels(candidates_PCA)
set.seed(123)
NN_labeled_PCA = labeled_PCA[, 1:numPcas]
NN_labeled_PCA$koi_disposition = ifelse(labeled_PCA$label == "CONFIRMED", 1, 0)
head(scaled_data)
#testing and training sets
num_samples = dim(scaled_data)[1]
sampling.rate = 0.8
training = sample(1:num_samples, sampling.rate * num_samples, replace = FALSE)
trainingSet.norm.PCA = subset(NN_labeled_PCA[training,])
testing = setdiff(1:num_samples, training)
testingSet.norm.PCA = subset(NN_labeled_PCA[testing, ])
sizeTestSet = dim(testingSet.norm)[1]
label.name = "label"
variable.names = rep(0, numPcas)

numCols = dim(testingSet.norm.PCA)[2]
variable.names = colnames(testingSet.norm.PCA)[1:numCols - 1]

nn.form <-
  create.formula(outcome.name = koiDP.name,
                 input.names = variable.names)
nn.form
$formula
koi_disposition ~ PC1 + PC2 + PC3 + PC4 + PC5 + PC6 + PC7 + PC8 + 
    PC9 + PC10 + PC11 + PC12 + PC13 + PC14 + PC15 + PC16 + PC17 + 
    PC18 + PC19 + PC20
<environment: 0x000002b164a761d0>

$inclusion.table

$interactions.table
library(neuralnet)
nnModel3 = neuralnet( #parameters tuned manually to ensure this stays computationally tractable
  nn.form,
  data = trainingSet.norm.PCA,
  hidden = c(5),
  linear.output = FALSE,
  act.fct = "logistic",
  stepmax=17000,
  threshold = 0.1
)

plot(nnModel3)


#Make prediction
predictedLabels = compute(nnModel3, testingSet.norm.PCA[, variable.names])

index = 1
threshold = .1
NNErrors = rep(0, 10)
NN_FPs = rep(0,10)
NN_FNs = rep(0,10)
while (threshold <= 1) { #tune threshold parameter
  results = data.frame(actual = testingSet.norm.PCA$koi_disposition,
                       prediction = predictedLabels$net.result)
  results$roundedPrediction = ifelse(results$prediction > threshold, 1, 0)
  error = sum(results$actual != results$roundedPrediction)
   Errors = AllErrors(results$actual,results$roundedPrediction,sizeTestSet,1)
  NNErrors[index] = Errors[[1]]
  NN_FPs[index] = Errors[[2]]
  NN_FNs[index] = Errors[[3]]
  threshold = threshold + .1
  index = index + 1
}
NNErrors #lowest error is with threshold = .6
 [1] 0.09196355 0.07705054 0.07456504 0.07125104 0.06876553 0.06628003 0.07539354 0.08367854 0.09444905
[10] 0.38856669
PCA_20_v_NeuralNetMisClassRate = NNErrors[order(NNErrors)[1]]
Avg_FP_PCA20NN = NN_FPs[order(NNErrors)[1]]
Avg_FN_PCA20NN = NN_FNs[order(NNErrors)[1]]
plot(
  x = 1:10 / 10,
  y = NNErrors,
  main = "Avg Error over thresholds for PCA Neural Network w/20 Vars",
  xlab = "Cutoff Threshold",
  ylab = "Misclassification Rate"
)
lines(x = 1:10 / 10, y = NNErrors)

As expected, this version has the lowest misclassification rate of 6.63%, which is 1.91% lower than the 13 variable PCA NN, and 3.4% lower than the original neural network. As you can see, however, we are reaching a point of diminishing returns; adding ~15% more variation only decreased the error rate by 1.91%

3.2.4) K-Nearest Neighbours

kNN works by computing the euclidean distances of the test features to training data points, known as “neighbours”. This model requires pre-processing because the distances from each data point must be in the same scale, therefore we first normalized the training and testing features. This model has an input parameter, k, which represents the number of neighbours considered. Both too-small and too-large values of k can be detrimental. To tune this parameter, we tested several values of k in a loop, selecting the k which resulted in the lowest misclassification rate.

numKs = 10
k_errors = rep(0, numKs)
k_FPs = rep(0,numKs)
k_FNs = rep(0,numKs)
for (ki in 1:numKs) {
  #tune k parameter
  avgErrors_fold = CreateErrorMatrix(NumFolds)
  avgFP_fold = CreateErrorMatrix(NumFolds)
  avgFN_fold = CreateErrorMatrix(NumFolds)
  for (fold in 1:NumFolds) {
    #k-fold cross validation
    set.seed(fold)
    #make normalized training and testing sets
    scaled_data = data.frame(scale(labeled_final[, 2:36]))
    scaled_data$koi_disposition = ifelse(labeled_final$koi_disposition == "CONFIRMED", 1, 0)
    summary(scaled_data)
    head(scaled_data)
    num_samples = dim(scaled_data)[1]
    sampling.rate = 0.8
    training = sample(1:num_samples, sampling.rate * num_samples, replace = FALSE)
    trainingSet.norm = subset(scaled_data[training,])
    testing = setdiff(1:num_samples, training)
    testingSet.norm = subset(scaled_data[testing, ])
    sizeTestSet = dim(testingSet.norm)[1]
    
    trainingfeatures = subset(trainingSet.norm, select = c(-koi_disposition))
    traininglabels = trainingSet.norm$koi_disposition
    testingfeatures = subset(testingSet.norm, select = c(-koi_disposition))
    testinglabels = testingSet.norm$koi_disposition
    
    
    #fit model and predict
    predictedLabels = knn(trainingfeatures, testingfeatures, traininglabels, k =
                            ki)
    #determine error
    error = sum(predictedLabels != testingSet.norm$koi_disposition)
    Errors = AllErrors(testingSet.norm$koi_disposition,
                       predictedLabels,
                       sizeTestSet,
                       1)
    misclassification_rate = error / sizeTestSet
    avgErrors_fold[fold] = Errors[[1]]
    avgFP_fold[fold] = Errors[[2]]
    avgFN_fold[fold] = Errors[[3]]
    
  }
  
  k_errors[ki] = mean(avgErrors_fold)
  k_FPs[ki] = mean(avgFP_fold)
  k_FNs[ki] = mean(avgFN_fold)
}
print(order(k_errors))
 [1]  4  6  8  7 10  5  9  3  2  1
#The lowest average error (this run) is from the model with k = 4.
AvgError_best_knn = k_errors[order(k_errors)[1]]
AvgFP_knn = k_FPs[order(k_errors)[1]]
AvgFN_knn = k_FNs[order(k_errors)[1]]

KNN produces an error of 9.08%, with an optimal k-value of 4

3.2.5) K-Means Clustering

Clustering is an unsupervised model which uses randomly generated centroids and assigns every point to a centroid based on euclidean distance. The model then iterates to find the centroid locations which minimize the distances to the data points in the clusters. Since this model also requires calculation of euclidean distance, the normalized data set was also used here.

Since our data set was labelled, the supervised models will likely yield a better misclassification rate than k-Means clustering. We include clustering in case the algorithm found unforeseen relationships in the unlabelled data features.

#scale full data set.
set.seed(123)
scaled_data = data.frame(scale(labeled_final[, 2:36]))
scaled_data$koi_disposition = ifelse(labeled_final$koi_disposition == "CONFIRMED", 2, 1)
num_samples = dim(scaled_data)[1]
features = subset(scaled_data, select = c(-koi_disposition))
#fit the model
kclustering = kmeans(features, centers = 2, nstart = 25)
#visualize the clusters
fviz_cluster(kclustering, data = features)

error = sum(scaled_data$koi_disposition != kclustering$cluster)
misclassification_rate = error / dim(scaled_data)[1]


isWrong = (scaled_data$koi_disposition != kclustering$cluster)
isRight = (scaled_data$koi_disposition == kclustering$cluster)
IsC = (kclustering$cluster == 2)
IsF = (kclustering$cluster == 1)
FalsePositives = sum(isWrong & IsC)
FalseNegatives = sum(isWrong & IsF)
TruePositives = sum(isRight & IsC)
TrueNegatives = sum(isRight & IsF)
Clustering_FP_Rate = (FalsePositives / (FalsePositives + TrueNegatives))#define FP rate as FP/(FP+TN)
Clustering_FN_Rate = (FalseNegatives / (FalseNegatives + TruePositives))#define FN rate as FN/(FN+TP)


AvgErrorClustering = misclassification_rate
if (AvgErrorClustering > .5) {
  AvgErrorClustering = 1 - AvgErrorClustering#since clustering does not know which cluster is CONFIRMED and which is FALSE POSITIVE, they can be flipped
  Clustering_FP_Rate = 1 - Clustering_FP_Rate
  Clustering_FN_Rate = 1 - Clustering_FN_Rate
  
}

Unsurprisingly, clustering has the largest error, of 41.83%; given this is an unsupervised method and our data has labels. Additionally, Clustering is especially terrible at predicting “FALSE POSITIVEs,” with a false negative rate of 99.56%

4) Select Best Model

error_output = data.frame(
  "Model" = c(
    "Decision Tree",
    "GLM",
    "Random Forest",
    "SVM",
    "Neural Net",
    "KNN",
    "Clustering",
    "PCA 15 Var NN",
    "PCA 20 Var NN",
    "XGBoost"
  ),
  "Misclassification Rate" = c(
    AvgErrorDT,
    AvgErrorGLM,
    AvgErrorRF,
    AvgErrorSVM,
    NeuralNetMisClassRate,
    AvgError_best_knn,
    AvgErrorClustering,
    PCA_15_v_NeuralNetMisClassRate,
    PCA_20_v_NeuralNetMisClassRate,
    AvgErrorXGB
  ), "False Positive Rate" = c(
    AvgFP_DT,
    AvgFP_GLM,
    AvgFP_RF,
    AvgFP_SVM,
    AvgFP_NN,
    AvgFP_knn,
    Clustering_FP_Rate,
    Avg_FP_PCA15NN,
    Avg_FP_PCA20NN,
    AvgFP_XGB
  ), "False Negative Rate" = c(
    AvgFN_DT,
    AvgFN_GLM,
    AvgFN_RF,
    AvgFN_SVM,
    AvgFN_NN,
    AvgFN_knn,
    Clustering_FN_Rate,
    Avg_FN_PCA15NN,
    Avg_FN_PCA20NN,
    AvgFN_XGB
  )
)
print(error_output)

XGBoost has the lowest misclassification rate; although it has a slightly higher False Positive rate than some other moddles, it still illustrates the best overall accuracy. We will remake this model using the full dataset.

#5 Make predictions

Remake the XGBoost model using the full dataset:

   xgData = data.matrix(labeled_final)
    xgData[, 1] = ifelse(xgData[, 1] == 2, 1, 0)
   
    xgBoostModelFinal = xgboost(
      data = xgData[, 2:36],
      label = xgData[, 1],
      max.depth = 6,
      eta = .22,
      nrounds = 100,
      verbose = 0,
      objective = "binary:logistic",
      eval_metric="error"
    )
    xgPredict = data.matrix(candidates_final)
    xgPredict[, 1] = ifelse(xgPredict[, 1] == 2, 1, 0)
    
   

Predict labels of candidates dataset, and write it to file

 #make predictions
BoostPredictions = predict(xgBoostModelFinal, data.matrix(candidates_final)[, 2:36])
BoostPredictionsRounded = ifelse(BoostPredictions > .4, 1, 0)
predictedLabels = ifelse(BoostPredictionsRounded == 1, "FALSE POSITIVE", "CONFIRMED")
candidates_final$koi_disposition = predictedLabels
head(candidates_final)
write.csv(candidates_final, "labeledCandidates.csv")
LS0tDQp0aXRsZTogIlIgTm90ZWJvb2siDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQojIEV4b3BsYW5ldCBDbGFzc2lmaWNhdGlvbiBQcm9ibGVtIA0KDQpGb3IgdGhpcyBwcm9qZWN0LCB3ZSBkZWNpZGVkIHRvIHBlcmZvcm0gY2xhc3NpZmljYXRpb24gb24gb3VyIGRhdGEgc2V0LiBDbGFzc2lmaWNhdGlvbiBpcyBhIHdheSB0byBwcmVkaWN0IGEgbGFiZWwsIGluIG91ciBjYXNlIGNhdGVnb3JpY2FsLCBiYXNlZCBvbiB0aGUgY2hhcmFjdGVyaXN0aWNzIG9mIHRoZSBkYXRhLiBUaGUNCg0KDQpUaGUgb2JqZWN0aXZlIG9mIG91ciBwcm9qZWN0IGlzIHRvIHVzZSBtYWNoaW5lIGxlYXJuaW5nIGNsYXNzaWZpY2F0aW9uIG1vZGVscyB0byBkZXRlcm1pbmUgaWYgYSBnaXZlbiBvYnNlcnZhdGlvbiBvZiBhIHN0YXIgc2hvdWxkIGJlIGNsYXNzaWZpZWQgYXMgYW4gZXhvcGxhbmV0IChhIHBsYW5ldCBvdXRzaWRlIG91ciBzb2xhciBzeXN0ZW0pLiBUaGUgb3JpZ2luYWwgZGF0YSBzZXQgY29tZXMgZnJvbSB0aGUgW0tlcGxlciBTcGFjZSBPYnNlcnZhdG9yeV0oaHR0cHM6Ly93d3cua2FnZ2xlLmNvbS9uYXNhL2tlcGxlci1leG9wbGFuZXQtc2VhcmNoLXJlc3VsdHMpLCBhbmQgZGV0YWlscyA5LDU2NCAgb2JzZXJ2YXRpb25zIG9mIHBvdGVudGlhbCBleG9wbGFuZXRzLCBhbG9uZyB3aXRoIDUwIGRlc2NyaXB0aXZlIGZlYXR1cmVzIHJhbmdpbmcgZnJvbSBpZGVudGlmaWVycyB0byBzcGVjaWZpYyBtZWFzdXJlbWVudHMuIFRoZSBjb2x1bW4g4oCca29pX2Rpc3Bvc2l0aW9u4oCdIGxhYmVsbGVkIGVhY2ggb2YgdGhlIHBvdGVudGlhbCBleG9wbGFuZXRzIGFzIGVpdGhlciDigJxDT05GSVJNRUQs4oCdIOKAnEZBTFNFIFBPU0lUSVZFLCIgb3IgIkNBTkRJREFURSIuIFRob3NlIGxhYmVsZWQgIkNBTkRJREFURSIgaGF2ZSBub3QgeWV0IGJlZW4gZGV0ZXJtaW5lZCB0byBiZSBleG9wbGFuZXRzIG9yIG5vdDsgdGhlIGdvYWwgb2Ygb3VyIGFuYWx5c2lzIHdpbGwgYmUgdG8gbGFiZWwgdGhlbSBhcyAiQ09ORklSTUVEIiBvciAiRkFMU0UgUE9TSVRJVkUuIiBOT1RFOiBJbiB0aGlzIGNvbnRleHQsICJmYWxzZSBwb3NpdGl2ZSIgZG9lcyBub3QgaW5kaWNhdGUgYSBmYWxzZSBwb3NpdGl2ZSBvdXRwdXQgZnJvbSBvdXIgbW9kZWwuIEluc3RlYWQsIGl0IHJlZmVycyB0byB0aGUgb3JpZ2luYWwgb2JzZXJ2YXRpb24sIHdoaWNoIHdhcyBjb25zaWRlcmVkIGEgY2FuZGlkYXRlLCB0byBoYXZlIGJlZW4gZmFsc2VseSBpZGVudGlmaWVkIGFzIGEgY2FuZGlkYXRlLg0KDQpUbyBzb2x2ZSB0aGlzIHByb2JsZW0sIHdlIHdpbGwgY2Fycnkgb3V0IHRoZSBmb2xsb3dpbmcgcHJvY2VzczoNCg0KMS4gTG9hZCwgcHJlcHJvY2VzcywgYW5kIGNsZWFuIHVwIGRhdGENCjIuIERhdGEgdmlzdWFsaXphdGlvbiBhbmQgZXhwbG9yYXRvcnkgZGF0YSBhbmFseXNpcw0KMy4gQ3JlYXRpb24gYW5kIHRlc3Rpbmcgb2YgY2FuZGlkYXRlIG1vZGVsczoNCiAgICArIDMuMSkgTW9kZWxzIHVzaW5nIG5vbi1zY2FsZWQgZGF0YToNCiAgICAgICAgLSAzLjEuMSkgRGVjaXNpb24gVHJlZSwgUmFuZG9tIEZvcmVzdCwgYW5kIFN1cHBvcnQgVmVjdG9yIE1hY2hpbmUNCiAgICAgICAgLSAzLjEuMikgWEdCb29zdA0KICAgICAgICAtIDMuMS4zKSBMb2dpc3RpYyBSZWdyZXNzaW9uDQogICAgKyAzLjIpIE1vZGVscyB1c2luZyBzY2FsZWQgZGF0YToNCiAgICAgICAgLSAzLjIuMSkgTmV1cmFsIE5ldHdvcms6IE9yaWdpbmFsIFZhcmlhYmxlcw0KICAgICAgICAtIDMuMi4yKSBOZXVyYWwgTmV0d29yazogUENBIHcvMTUgVmFyaWFibGVzDQogICAgICAgIC0gMy4yLjMpIE5ldXJhbCBOZXR3b3JrOiBQQ0Egdy8yMCBWYXJpYWJsZXMNCiAgICAgICAgLSAzLjIuNCkgSy1OZWFyZXN0IE5laWdoYm91cnMNCiAgICAgICAgLSAzLjIuNSkgSy1NZWFucyBDbHVzdGVyaW5nDQo0LiBTZWxlY3QgYmVzdCBtb2RlbA0KNS4gVXNlIGJlc3QgbW9kZWwgdG8gbWFrZSBwcmVkaWN0aW9ucw0KDQojIDEuIERhdGEgTG9hZGluZywgUHJlLVByb2Nlc3NpbmcgYW5kIENsZWFuIFVwDQoNCkxvYWQgTGlicmFyaWVzOg0KYGBge3J9DQpsaWJyYXJ5KE1hdHJpeCkgICAgI2V4dHJhIE1hdHJpeCBmdW5jdGlvbmFsaXR5DQpsaWJyYXJ5KHJwYXJ0KSAgICAgICNEZWNpc2lvbiB0cmVlcw0KbGlicmFyeShycGFydC5wbG90KQ0KbGlicmFyeShyYW5kb21Gb3Jlc3QpI3JhbmRvbSBmb3Jlc3QNCmxpYnJhcnkoY2xhc3MpICAgICAgI0tOTg0KbGlicmFyeShlMTA3MSkgICAgICAjbWlzYy4gc3RhdHMgZnVuY3Rpb25hcnkNCmxpYnJhcnkoeGdib29zdCkgICAgI1hHQm9vc3QgQWxnb3JpdGhtDQpsaWJyYXJ5KEZOTikgICAgICAgICNLTk4NCmxpYnJhcnkoZmFjdG9leHRyYSkgI1BDQSBhbmQgY2x1c3RlcmluZw0KbGlicmFyeShnZ3Bsb3QyKSAgICAjZXh0cmEgcGxvdHRpbmcgZnVuY3Rpb25hbGl0eQ0KbGlicmFyeShjb3JycGxvdCkgICAjZXh0cmEgcGxvdHRpbmcgZnVuY3Rpb25hbGl0eQ0KI3Zpc3VhbGl6YXRpb24gcGFja2FnZXMNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KHJlc2hhcGUyKQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KERpYWdyYW1tZVIpICNwbG90dGluZyBmb3IgWEdCb29zdA0KbGlicmFyeShmb3JtdWxhaWMpICNhdXRvbWF0ZWQgZm9ybXVsYSBjcmVhdGlvbg0KbGlicmFyeShuZXVyYWxuZXQpICNOZXVyYWwgTmV0d29ya3MNCmBgYA0KDQoNClRoZSBmaXJzdCBzdGVwIHdhcyB0byBsb2FkIHRoZSBkYXRhLCBhbmQgY29udmVydCBvdXIgb3V0cHV0IHZhcmlhYmxlLCAia29pX2Rpc3Bvc2l0aW9uIiwgdG8gYSBmYWN0b3INCmBgYHtyfQ0KI3JlYWQgZGF0YQ0Kb3JpZ2luYWxLZXBwbGVyRGF0YSA9IHJlYWQuY3N2KCJjdW11bGF0aXZlLmNzdiIpICNyZWFkIGRhdGENCmZhY3RvcmVkX2tlcHBsZXJfZGF0YSA9IG9yaWdpbmFsS2VwcGxlckRhdGENCmZhY3RvcmVkX2tlcHBsZXJfZGF0YSRrb2lfZGlzcG9zaXRpb24gPSBmYWN0b3Iob3JpZ2luYWxLZXBwbGVyRGF0YSRrb2lfZGlzcG9zaXRpb24pICNmYWN0b3IgY2hhcmFjdGVyIGRhdGENCmZhY3RvcmVkX2tlcHBsZXJfZGF0YSRrb2lfcGRpc3Bvc2l0aW9uID0gZmFjdG9yKG9yaWdpbmFsS2VwcGxlckRhdGEka29pX3BkaXNwb3NpdGlvbikNCmhlYWQoZmFjdG9yZWRfa2VwcGxlcl9kYXRhKQ0KYGBgDQpPdXIgbmV4dCBzdGVwIHdhcyB0byBjb25kdWN0IGZvcm1zIG9mIGRpbWVuc2lvbmFsaXR5IHJlZHVjdGlvbiBvbiBvdXIgNDkgcG9zc2libGUgaW5wdXQgdmFyaWFibGVzLiBEaW1lbnNpb25hbGl0eSByZWR1Y3Rpb24gaXMgaW1wb3J0YW50IGZvciBvdXIgbGFyZ2UgZGF0YSBzZXQgdG8gcmVkdWNlIHRoZSBjb21wdXRhdGlvbmFsIHJlcXVpcmVtZW50cyBvZiBtb2RlbHMgYW5kIHJlZHVjZSBwb3NzaWJsZSBvdmVyZml0dGluZyBvciBiaWFzIGZyb20gdW4taW1wb3J0YW50IGZlYXR1cmVzLiBGaXJzdCwgd2UgcmVtb3ZlZCBzZXZlcmFsIGVudGlyZWx5IGVtcHR5IGNvbHVtbnMgKHdpdGggdmFsdWVzIG9mIDAgb3IgTkEpLCB1bmlxdWUgcm93IGlkZW50aWZpZXJzIChvYnNlcnZhdGlvbiBudW1iZXJzKSwgYXMgd2VsbCBhcyBzZXZlcmFsIHdoaWNoIGNvdWxkIG9ubHkgYmUgZmlsbGVkIG9uY2UgdGhlIG91dGNvbWUgb2YgdGhlIG9ic2VydmF0aW9uIHdhcyBhbHJlYWR5IGtub3duLiBGb3IgZXhhbXBsZSwg4oCca2VwbGVyX25hbWXigJ0gaXMgdGhlIG5hbWUgZ2l2ZW4gdG8gYSBjb25maXJtZWQgZXhvcGxhbmV0LCBzbyBpdCB3YXMgcmVtb3ZlZCBmcm9tIHRoZSBkYXRhIHNldC4gVGhlcmUgd2VyZSBubyB1bmlxdWUgb3V0bHlpbmcgZGF0YSBwb2ludHMgdGhhdCBoYWQgdG8gYmUgZXhwbG9yZWQgZnVydGhlci4gDQoNCmBgYHtyfQ0KI3JlbW92ZSBhbGwtTkEgY29sdW1uczoNCnJlbW92ZV9LT0lfdGVjaF9mYWN0b3JlZCA9IHN1YnNldChmYWN0b3JlZF9rZXBwbGVyX2RhdGEsIHNlbGVjdCA9IC1jKGtvaV90ZXFfZXJyMSkpICNyZW1vdmUgDQpyZW1vdmVfS09JX3RlY2hfZmFjdG9yZWQgPSBzdWJzZXQocmVtb3ZlX0tPSV90ZWNoX2ZhY3RvcmVkLCBzZWxlY3QgPSAtYyhrb2lfdGVxX2VycjIpKQ0KcmVtb3ZlX05BcyA9IG5hLmV4Y2x1ZGUocmVtb3ZlX0tPSV90ZWNoX2ZhY3RvcmVkKSAjcmVtb3ZlIGFueSByb3dzIHdpdGggTkFzIHJlbWFpbmluZw0KDQojUmVtb3ZlIHVuaXF1ZSBpZGVudGlmaWVycyBhbmQgcm93cyB0aGF0IGNhbiBvbmx5IGJlIGtub3duIGFmdGVyIHRoZSBzdGF0dXMgb2YgYSBwbGFuZXQgaXMga25vd24uIFRoZXkgdGhlcmVmb3JlIGFyZSBub3QgdXNlZnVsIGlucHV0cyB0byBkZXRlcm1pbmUgdGhlIHN0YXR1cyBvZiBhIGNhbmRpZGF0ZS4NCmlkZW50aWZpZXJzX3JlbW92ZWQgPSBzdWJzZXQoDQogIHJlbW92ZV9OQXMsDQogIHNlbGVjdCA9IC1jKA0KICAgIHJvd2lkLA0KICAgIGtlcGlkLA0KICAgIGtlcG9pX25hbWUsDQogICAga2VwbGVyX25hbWUsDQogICAga29pX3BkaXNwb3NpdGlvbiwNCiAgICBrb2lfc2NvcmUNCiAgKQ0KKQ0KDQojQWxzbyBuZWVkcyB0byByZW1vdmUgZmxhZ3MsIHRoZXNlIGNhbiBvbmx5IGJlIGtub3duIGFmdGVyIHN0YXR1cyBpcyBjb25maXJtZWQNCmlkZW50aWZpZXJzX3JlbW92ZWQgPSBzdWJzZXQoDQogIGlkZW50aWZpZXJzX3JlbW92ZWQsDQogIHNlbGVjdCA9IC1jKA0KICAgIGtvaV9mcGZsYWdfc3MsDQogICAga29pX2ZwZmxhZ19lYywNCiAgICBrb2lfZnBmbGFnX2NvLA0KICAgIGtvaV9mcGZsYWdfbnQsDQogICAga29pX3RjZV9wbG50X251bSwNCiAgICBrb2lfdGNlX2RlbGl2bmFtZQ0KICApDQopDQoNCiNOb3csIHdlIHdpbGwgc2VwYXJhdGUgaW4gbGFiZWxlZCBhbmQgdW5sYWJlbGVkIGRhdGEuIFRoZSBsYWJlbGVkIGRhdGEgd2lsbCBiZSB1c2VkIHRvIHRyYWluIG91ciBtb2RlbDsgb25jZSB0aGUgYmVzdCBtb2RlbCBpcyBzZWxlY3RlZCwgd2Ugd2lsbCB1c2UgaXQgdG8gcHJlZGljdCB0aGUgbGFiZWxzIG9mIHRoZSB1bmxhYmVsZWQgZGF0YXNldA0KY2FuZGlkYXRlc19maW5hbCA9IGlkZW50aWZpZXJzX3JlbW92ZWRbaWRlbnRpZmllcnNfcmVtb3ZlZCRrb2lfZGlzcG9zaXRpb24gPT0NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkNBTkRJREFURSIsXSAjc2VwYXJhdGUgb3V0IGp1c3QgdGhlIGNhbmRpZGF0ZXMNCmxhYmVsZWRfZmluYWwgPSBpZGVudGlmaWVyc19yZW1vdmVkW2lkZW50aWZpZXJzX3JlbW92ZWQka29pX2Rpc3Bvc2l0aW9uICE9DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJDQU5ESURBVEUiLF0NCmxhYmVsZWRfZmluYWwgPSBkcm9wbGV2ZWxzKGxhYmVsZWRfZmluYWwpICAgICAgIA0KY2FuZGlkYXRlc19maW5hbCA9IGRyb3BsZXZlbHMoY2FuZGlkYXRlc19maW5hbCkNCg0KbnVtRmFsc2UgPSBzdW0obGFiZWxlZF9maW5hbCRrb2lfZGlzcG9zaXRpb249PSJGQUxTRSBQT1NJVElWRSIpDQpudW1Db25maXJtZWQgPSBzdW0obGFiZWxlZF9maW5hbCRrb2lfZGlzcG9zaXRpb249PSJDT05GSVJNRUQiKQ0KIyNjaGVjaw0KbnVtRmFsc2UgKyBudW1Db25maXJtZWQgPT0gZGltKGxhYmVsZWRfZmluYWwpWzFdDQoNClByb3BvcnRpb25zID0gZGF0YS5mcmFtZShsYWJlbCA9IGMoIkNPTkZJUk1FRCIsIkZBTFNFIFBPU0lUSVZFIiksIG51bWJlciA9IGMobnVtQ29uZmlybWVkLG51bUZhbHNlKSkNCmJwID0gZ2dwbG90KFByb3BvcnRpb25zLGFlcyh4ID0gIiIseT1udW1iZXIsZmlsbD1sYWJlbCkpK2dlb21fYmFyKHdpZHRoID0gMSxzdGF0ID0gImlkZW50aXR5IikNCnBpZSA9IGJwK2Nvb3JkX3BvbGFyKCJ5Iiwgc3RhcnQgPSAwKSArIGdndGl0bGUoIlByb3BvcnRpb25zIG9mIGxhYmVsZWRfZmluYWwgdGhhdCBhcmUgQ09ORklSTUVEIG9yIEZBTFNFIFBPU0lUSVZFIikNCnBpZQ0KaGVhZChsYWJlbGVkX2ZpbmFsKQ0KaGVhZChjYW5kaWRhdGVzX2ZpbmFsKQ0KYGBgDQpXZSBhcmUgbGVmdCB3aXRoIHR3byBkYXRhc2V0czoNCg0KIC0gY2FuZGlkYXRlc19maW5hbCwgd2hpY2ggaGFzIDE3NzIgZGF0YSBwb2ludHMgYW5kIDM1IGZlYXR1cmVzLCB3aGljaCB3aWxsIGJlIHdoYXQgd2UgdXNlIHRoZSBiZXN0IG1vZGVsIG9uDQogLSBsYWJlbGVkX2ZpbmFsLCB3aGljaCBoYXMgNjAzMSBkYXRhIHBvaW50cyBhbmQgMzUgZmVhdHVyZXMsIHdoaWNoIHdpbGwgYmUgd2hhdCB3ZSB1c2UgdG8gdHJhaW4gYW5kIHRlc3QgY2FuZGlkYXRlIG1vZGVscywgYXMgd2VsbCBhcyBidWlsZCBvdXIgZmluYWwgbW9kZWwuDQoNCiMgMikgRGF0YSBWaXN1YWxpemF0aW9uIGFuZCBFeHBsb3JhdG9yeSBEYXRhIEFuYWx5c2lzDQpTZWxlY3QgdGhlIHZhcmlhYmxlcyB0byBiZSB1c2VkOg0KYGBge3J9DQojQWxsIHZhcmlhYmxlcyBoYXZlIGVycm9ycyAtIGZvciBleHBsb3JhdG9yeSBkYXRhIGFuYWx5c2lzIHdlIHdpbGwgb25seSBiZSBsb29raW5nIGF0IHRoZSB2YXJpYWJsZXMgdGhlbXNlbHZlcywgbm90IHRoZWlyIGVycm9ycw0KdmFyaWFibGVzb25seSA9IGxhYmVsZWRfZmluYWwgJT4lDQogIHNlbGVjdChrb2lfcGVyaW9kLCBrb2lfdGltZTBiaywga29pX2ltcGFjdCwga29pX2R1cmF0aW9uLCBrb2lfZGVwdGgsIGtvaV9wcmFkLCBrb2lfdGVxLCBrb2lfaW5zb2wsIGtvaV9tb2RlbF9zbnIsIGtvaV9zdGVmZiwga29pX3Nsb2dnLCBrb2lfc3JhZCwgcmEsIGRlYywga29pX2tlcG1hZykNCmBgYA0KDQoNCkNyZWF0ZSBhIGNvcnJlbGF0aW9uIGhlYXQgbWFwIHdpdGggb25seSB0aGUgdmFyaWFibGVzIChleGNsdWRpbmcgdGhlIGVycm9ycykuDQpgYGB7cn0NCmNvcm1hdCA8LSByb3VuZChjb3IodmFyaWFibGVzb25seSksMikNCmhlYWQoY29ybWF0KQ0KDQptZWx0ZWRfY29ybWF0IDwtIG1lbHQoY29ybWF0KQ0KaGVhZChtZWx0ZWRfY29ybWF0KQ0KDQpnZ3Bsb3QoZGF0YSA9IG1lbHRlZF9jb3JtYXQsIGFlcyh4PVZhcjEsIHk9VmFyMiwgZmlsbD12YWx1ZSkpICsgDQogIGdlb21fdGlsZSgpDQpgYGANCiBEZWZpbmluZyBzaWduaWZpY2FudCBjb3JyZWxhdGlvbnMgYXMgdGhvc2UgZ3JlYXRlciB0aGFuIC40LCB0aGVyZSBhcmUgYSBmZXc7IHRoaXMgd2lsbCBuZWVkIHRvIGJlIGRlYWx0IHdpdGggaW4gTG9naXN0aWMgUmVncmVzc2lvbiwgYnV0IHNob3VsZCBub3QgYWZmZWN0IG90aGVyIG1vZGVscy4gDQoNCiMjIyMgUGxvdHMgYnkgRkFMU0UgUE9TSVRJVkUgYW5kIENPTkZJUk1FRA0KDQpXZSB3aWxsIG5vdyBvYnNlcnZlIHRoZSBkaXN0cmlidXRpb24gb2Ygc2V2ZXJhbCB2YXJpYWJsZXMgYWNyb3NzIGZhbHNlIHBvc2l0aXZlIGFuZCBjb25maXJtZWQgZXhvcGxhbmV0cy4gVGhpcyB3aWxsIGhlbHAgZGV0ZXJtaW5lIHdoZXJlIHRoZXJlIGlzIGEgY2xlYXIgcGF0dGVybiBpbiBjZXJ0YWluIHZhcmlhYmxlcy4NCmBgYHtyfQ0KZ2dwbG90KGRhdGEgPSBsYWJlbGVkX2ZpbmFsKSArDQogIGdlb21fcG9pbnQobWFwcGluZyA9IGFlcyh4ID0ga29pX3BlcmlvZCwgeSA9IGtvaV90aW1lMGJrKSkgKyANCiAgZmFjZXRfd3JhcCh+IGtvaV9kaXNwb3NpdGlvbiwgbnJvdyA9IDIpDQpgYGANCkZvciBrb2lfcGVyaW9kIGFuZCBrb2lfdGltZTBiaywgdGhlIGRpc3RyaWJ1dGlvbnMgb2YgdGhlIHZhcmlhYmxlcyBzZWVtIHNpbWlsYXJzLCB3aXRoIHNvbWUgb3V0bGllcnMuIFRoYXQgYmVpbmcgc2FpZCwgRkFMU0UgUE9TSVRJVkVzIGNhbiBoYXZlIHNpZ25pZmljYW50bHkgaGlnaGVyIGtvaV9wZXJpb2RzIHRoYW4gQ09ORklSTUVEcw0KYGBge3J9DQpnZ3Bsb3QoZGF0YSA9IGxhYmVsZWRfZmluYWwpICsNCiAgZ2VvbV9wb2ludChtYXBwaW5nID0gYWVzKHggPSBrb2lfaW1wYWN0LCB5ID0ga29pX2R1cmF0aW9uKSkgKyANCiAgZmFjZXRfd3JhcCh+IGtvaV9kaXNwb3NpdGlvbiwgbnJvdyA9IDIpDQpgYGANClRoZSBwbG90IG9mIGtvaV9pbXBhY3QgdnMuIGtvaV9kdXJhdGlvbiBzaG93cyBjbGVhciBkaWZmZXJlbmNlcyBiZXR3ZWVuIHRoZWlyIGRpc3RyaWJ1dGlvbnMgLSBDT05GSVJNRURzIGhhdmUgYSBzaWduaWZpY2FudGx5IHNtYWxsIHJhbmdlIGZvciBib3RoIHZhcmlhYmxlcywgYWx0aG91Z2ggdGhvc2UgY2FuZGlkYXRlcyB0aGF0IGZhbGwgaW5zaWRlIHRoYXQgcmFuZ2UgbWF5IGJlIGRpZmZpY3VsdCB0byBjbGFzc2ZpeS4NCmBgYHtyfQ0KZ2dwbG90KGRhdGEgPSBsYWJlbGVkX2ZpbmFsKSArDQogIGdlb21fcG9pbnQobWFwcGluZyA9IGFlcyh4ID0ga29pX2RlcHRoLCB5ID0ga29pX3ByYWQpKSArIA0KICBmYWNldF93cmFwKH4ga29pX2Rpc3Bvc2l0aW9uLCBucm93ID0gMikNCmBgYA0Ka29pX2RlcHRoIHNob3dzIGEgY2xlYXIgZGlmZmVyZW50OiBoaWdoIGtvaV9kZXB0aHMgYWxtb3N0IGFsd2F5cyBpbmRpY2F0ZSBGQUxTRSBQT1NJVElWRXMNCmBgYHtyfQ0KZ2dwbG90KGRhdGEgPSBsYWJlbGVkX2ZpbmFsKSArDQogIGdlb21fcG9pbnQobWFwcGluZyA9IGFlcyh4ID0ga29pX3RlcSwgeSA9IGtvaV9pbnNvbCkpICsgDQogIGZhY2V0X3dyYXAofiBrb2lfZGlzcG9zaXRpb24sIG5yb3cgPSAyKQ0KYGBgDQprb2lfdGVxIGhhcyBhIHNpZ25pZmljYW50bHkgc21hbGxlciByYW5nZSBmb3IgQ09ORklSTUVEIGRhdGEgcG9pbnRzLCBhbmQgS09JX2luc29sIHN0YXlzIGNsb3NlciB0byB6ZXJvLiBJZiB3ZSBsb29rIGF0IA0KYGBge3J9DQpzdW1tYXJ5KGxhYmVsZWRfZmluYWwka29pX2luc29sKQ0Kc3VtbWFyeShsYWJlbGVkX2ZpbmFsJGtvaV90ZXEpDQpgYGANClRoaXMgY29uZmlybXMgdGhhdCB0aGVyZSBhcmUgdmVyeSBsYXJnZSB2YWx1ZXMgZm9yIEZBTFNFIFBPU0lUSVZFUyB0aGF0IGFyZSBzaWduaWZpY2FudGx5IG91dHNpZGUgdGhlIG5vcm1hbCBkYXRhIHJhbmdlLiBJdCB3aWxsIGJlIGVhc3kgdG8gY2xhc3NpZnkgdGhlc2UgYXMgRkFMU0UgUE9TSVRJVkVzLiBXZSBjYW4gZXhwbG9yZSB0aGVzZSBncmFwaHMgd2l0aCBhIHNtYWxsZXIgcmFuZ2UsIHRvIGV4Y2x1ZGUgdGhlIG91dGxpZXJzOg0KDQpgYGB7cn0NCmdncGxvdChkYXRhID0gbGFiZWxlZF9maW5hbFtsYWJlbGVkX2ZpbmFsJGtvaV9pbnNvbDwyNTAwMDAsXSkgKw0KICBnZW9tX3BvaW50KG1hcHBpbmcgPSBhZXMoeCA9IGtvaV90ZXEsIHkgPSBrb2lfaW5zb2wpKSArIA0KICBmYWNldF93cmFwKH4ga29pX2Rpc3Bvc2l0aW9uLCBucm93ID0gMikNCmBgYA0KVGhpcyBzaG93cyB0aGF0IGZvciB2YWx1ZXMgb2Yga29pX2luc29sIDwgMjUwMDAgYW5kIGtvaV90ZXE8MzAwMCwgaXQgYmVjb21lcyBkaWZmaWN1bHQgdG8gaWRlbnRpZnkgd2hldGhlciBhIGdpdmVuIGRhdGEgcG9pbnQgaXMgRkFMU0UgUE9TSVRJVkUgb3IgQ09ORklSTUVELg0KYGBge3J9DQpnZ3Bsb3QoZGF0YSA9IGxhYmVsZWRfZmluYWwpICsNCiAgZ2VvbV9wb2ludChtYXBwaW5nID0gYWVzKHggPSBrb2lfbW9kZWxfc25yLCB5ID0ga29pX3N0ZWZmKSkgKyANCiAgZmFjZXRfd3JhcCh+IGtvaV9kaXNwb3NpdGlvbiwgbnJvdyA9IDIpDQpgYGANCk9uY2UgYWdhaW4sIEZBTFNFIFBPU0lUSVZFUyBzaG93IHNpZ25pZmljYW50bHkgbW9yZSB2YXJpYWJpbGl0eSBvbiBib3RoIGF4ZXMgdGhhbiBDT05GSVJNRURzDQpgYGB7cn0NCmdncGxvdChkYXRhID0gbGFiZWxlZF9maW5hbCkgKw0KICBnZW9tX3BvaW50KG1hcHBpbmcgPSBhZXMoeCA9IGtvaV9zbG9nZywgeSA9IGtvaV9zcmFkKSkgKyANCiAgZmFjZXRfd3JhcCh+IGtvaV9kaXNwb3NpdGlvbiwgbnJvdyA9IDIpDQpgYGANCkZBTFNFIFBPU0lUSVZFcyBjYW4gaGF2ZSBzaWduaWZpY2FudGx5IGxvd2VyIGtvaV9zbG9nZ3MgYW5kIGhpZ2hlciBrb2lfc3JhZHMgdGhhbiBDT05GSVJNRURzDQpgYGB7cn0NCmdncGxvdChkYXRhID0gbGFiZWxlZF9maW5hbCkgKw0KICBnZW9tX3BvaW50KG1hcHBpbmcgPSBhZXMoeCA9IHJhLCB5ID0gZGVjKSkgKw0KICBmYWNldF93cmFwKCB+IGtvaV9kaXNwb3NpdGlvbiwgbnJvdyA9IDIpDQpgYGANCkxhc3RseSwgZm9yIHJhIGFuZCBkZWMsIHRoZXJlIGlzIG5vIGNsZWFyIGRpZmZlcmVudCBpbiBkaXN0cmlidXRpb24gZm9yIENPTkZJUk1FRCBvciBGQUxTRSBQT1NJVElWRS4NCg0KT3ZlcmFsbCwgYW4gZWFzeSB3YXkgdG8gZGV0ZXJtaW5lIEZBTFNFIFBPU0lUSVZFcyB0ZW5kcyB0byBiZSB0byBsb29rIGZvciBkYXRhIHBvaW50cyBvdXRzaWRlIGEgZ2l2ZW4gcmFuZ2UuIEluc2lkZSB0aGF0IHJhbmdlLCB0aGUgZGV0ZXJtaW5hdGlvbiBvZiB3aGV0aGVyIGFuIG9ic2VydmF0aW9uIGlzIGEgZXhvcGxhbmV0IG9yIG5vdCBiZWNvbWVzIG1vcmUgZGlmZmljdWx0LiANCg0KIyAzLiBDcmVhdGlvbiBhbmQgdGVzdGluZyBvZiBjYW5kaWRhdGUgbW9kZWxzDQoNClR0byBjbGFzc2lmeSB0aGUgZGF0YSBhbmQgZGV0ZXJtaW5lIHdoaWNoIG9ic2VydmF0aW9uIGFyZSwgaW4gZmFjdCwgcGxhbmV0cywgb3VyIGdyb3VwIGZpcnN0IGhhZCB0byBkZXRlcm1pbmUgd2hpY2ggbW9kZWwgbW9zdCBhY2N1cmF0ZWx5IHByZWRpY3RlZCBvdXIgbGFiZWxlZCBkYXRhIHNldC4gV2UgZmlyc3QgZXhhbWluZWQgbW9kZWxzIHRoYXQgZGlkIG5vdCByZXF1aXJlIHNjYWxpbmcsIHRoZXNlIGluY2x1ZGVkOiBEZWNpc2lvbiBUcmVlLCBSYW5kb20gRm9yZXN0LCBYR0Jvb3N0LCBMb2dpc3RpYyBSZWdyZXNzaW9uLCBhbmQgU3VwcG9ydCBWZWN0b3IgTWFjaGluZXMgKFNWTSkuIE5leHQsIHdlIGV4YW1pbmVkIG1vZGVscyB0aGF0IHJlcXVpcmVkIHNjYWxpbmcsIHRoZXNlIGluY2x1ZGVkOiBLTk4sIE5ldXJhbCBOZXR3b3JrICh3aXRoIGFuZCB3aXRob3V0IFByaW5jaXBhbCBDb21wb25lbnQgQW5hbHlzaXMpLCBhbmQgSy1tZWFucyBjbHVzdGVyaW5nLiBUbyBjaG9vc2UgdGhlIGJlc3QgbW9kZWwgZm9yIG91ciBkYXRhIHNldCwgb3VyIGdyb3VwIGRlY2lkZWQgdG8gbG9vayBhdCB3aGljaCBtb2RlbCBwcm9kdWNlZCB0aGUgbG93ZXN0IG1pc2NsYXNzaWZpY2F0aW9uIHJhdGUuIEhvd2V2ZXIsIGl0IHNob3VsZCBiZSBub3RlZCB0aGF0IGluIHJlYWwtbGlmZSBhcHBsaWNhdGlvbiBhIG51bWJlciBvZiBvdGhlciBtZXRyaWNzIHNob3VsZCBiZSBjb25zaWRlcmVkIGFzIHdlbGwuIFRoZXNlIGluY2x1ZGUsIGJ1dCBhcmUgbm90IGxpbWl0ZWQgdG8sIHByZWNpc2lvbiBhbmFseXNpcyAoUFIgY3VydmVzKSwgcmVjYWxsLCBST0MtQVVDLCBhbmQgRjEgU2NvcmVzLiANCg0KV2hlbiBydW5uaW5nIHRoZSB2YXJpb3VzIG1vZGVscywgb3VyIGdyb3VwIGluY29ycG9yYXRlZCBLLWZvbGQgY3Jvc3MtdmFsaWRhdGlvbi4gVGhlIGJlbmVmaXQgb2YgdGhpcyBhcHByb2FjaCBpcyB0aGF0IGl0IGFsbG93cyB0aGUgbW9kZWwgdG8gYmVjb21lIG1vcmUgZ2VuZXJhbGl6ZWQsIGhlbHBpbmcgd2l0aCBvdmVyLWZpdHRpbmcgY29uY2VybnMuIEluIGFkZGl0aW9uLCB3ZSB0YWtlIHRoZSBhdmVyYWdlIHJlc3VsdCBvZiBmaXZlIG1pc2NsYXNzaWZpY2F0aW9uIHJhdGVzIGZvciBlYWNoIG1vZGVsLCBzaWduaWZpY2FudGx5IGxvd2VyaW5nIHRoZSBjaGFuY2UgdGhhdCBhIGdpdmVuIG1pc2NsYXNzaWZpY2F0aW9uIHJhdGUgaXMgcHJvZHVjZWQgb25seSBieSBjaGFuY2UgZHVlIHRvIGEgc3BlY2lmaWMgdGVzdGluZy90cmFpbmluZyBzZXQuIFRoaXMgYWxsb3dzIHVzIHRvIGJlIG1vcmUgY29uZmlkZW50IHRoYXQgdGhlIG1vZGVsIHdpdGggdGhlIGxvd2VzdCBtaXNjbGFzc2lmaWNhdGlvbiByYXRlIHRydWx5IGlzIHRoZSBiZXN0LiBEdWUgdG8gY29tcHV0YXRpb25hbCBsaW1pdHMsIHdlIGRlY2lkZWQgdG8gdXNlIGEgSyB2YWx1ZSBvZiA1IGFzIHdlIGJlbGlldmVkIHRoYXQgd291bGQgYmUgYWRlcXVhdGUgZm9yIG91ciBwdXJwb3Nlcy4gRmluYWxseSwgd2hlbiBkZWNpZGluZyB0aGUgcHJvcG9ydGlvbiBvZiB0cmFpbmluZyBhbmQgdGVzdGluZyBzZXQsIHdlIGRlY2lkZWQgdG8gZm9sbG93IGNsYXNzIHN0YW5kYXJkcyB3aXRoIDgwJSB0cmFpbmluZyBzZXQgYW5kIDIwJSB0ZXN0aW5nIHNldC4gT3VyIHNwZWNpZmljIGRhdGEgc2V0IGlzIGZhaXJseSBsYXJnZSB3aXRoICB+NiwwMzEgcm93czsgd2UgYmVsaWV2ZSBoYXZpbmcgfjQ4MDAgcm93cyB0byB0cmFpbiBhbmQgfjEsMjAwIHJvd3MgdG8gdGVzdCBpcyBsYXJnZSBlbm91Z2ggZm9yIGVhY2ggcHVycG9zZS4gDQoNCiMjIDMuMSkgTW9kZWxzIHVzaW5nIG5vbi1zY2FsZWQgZGF0YToNCg0KIyMjIDMuMS4xKSBEZWNpc2lvbiBUcmVlLCBSYW5kb20gRm9yZXN0LCBhbmQgU1ZNDQogICAgICAgIA0KVGhlIGdvYWwgb2YgYSBkZWNpc2lvbiB0cmVlIGlzIHRvIG1ha2Ugc3BsaXR0aW5nIGRlY2lzaW9ucyBvbiB0aGUgZGF0YSwgaW4gYW4gZWZmb3J0IHRvIG1pbmltaXplIHRoZSBsZWFzdCBzcXVhcmVzLCB0aHVzIGNyZWF0aW5nIGEgdHJlZS1saWtlIHN0cnVjdHVyZS4gVGhlc2UgbW9kZWxzIGFyZSB1c2VmdWwgYXMgYSBzdGFydGluZyBwb2ludCBiZWNhdXNlIHRoZXkgYXJlIGVhc3kgdG8gaW50ZXJwcmV0IGFzIHRoZSBwbG90IGNhbiBkaXNwbGF5IHdoaWNoIHZhcmlhYmxlcyBoYXZlIHRoZSBoaWdoZXN0IGltcG9ydGFuY2UgaW4gdGhlIHRyZWUuIE5vcm1hbGl6YXRpb24gYW5kIG90aGVyIGRhdGEgY2xlYW5pbmcgaXMgYWxzbyBub3QgcmVxdWlyZWQgZm9yIHRoaXMgbW9kZWwuICBBZGRpbmcgbW9yZSBkZXB0aCB0byB0aGUgdHJlZSBjYW4gcmVkdWNlIHRoZSBmaXR0aW5nIGVycm9yIHRvIHRoZSBkYXRhLCBidXQgaXQgY2FuIGxlYWQgdG8gb3ZlcmZpdHRpbmcgdGhlIG1vZGVsLiBBcyBhIHJlc3VsdCwgdGhlIGNvbXBsZXhpdHkgcGFyYW1ldGVyLCBjcCwgbXVzdCBiZSB0dW5lZCB0byBlbnN1cmUgdGhhdCB0aGUgb3B0aW1hbCBkZXB0aC10by1maXQgb2YgdGhlIG1vZGVsIGlzIHVzZWQuIEFzIHRoZSBjcCB2YWx1ZSBkZWNyZWFzZXMsIHNvIGRvZXMgdGhlIHJlbGF0aXZlIGVycm9yIGluIHRoZSBtb2RlbC4gV2UgYXV0b21hdGljYWxseSBwcnVuZSBvdXIgZGVjaXNpb24gdHJlZSB0byBzZWxlY3QgdGhlIGNwIHdoZXJlIHRoZSBjaGFuZ2UgaW4gZXJyb3IgaXMgbGVzcyB0aGFuIDAuMDU7IHRoaXMgaXMgb3VyIGZpcnN0IGV4YW1wbGUgb2YgcGFyYW1ldGVyIHR1bmluZy4gDQoNClJhbmRvbSBmb3Jlc3QgbW9kZWxzIGFyZSBtb3JlIGFjY3VyYXRlIGFuZCByb2J1c3QgYnV0IGhhcmRlciB0byBpbnRlcnByZXQgdGhhbiBhIHNpbmdsZSB0cmVlLiBUaGUgbW9kZWwgY3JlYXRlcyBtYW55IGRlY2lzaW9uIHRyZWVzIHdpdGggZGlmZmVyZW50IHJhbmRvbWl6ZWQgbGVhcm5pbmcgYW5kIHRlc3Rpbmcgc2V0cywgdGhlbiB0aGUgdHJlZXMg4oCcdm90ZeKAnSBvciDigJxhdmVyYWdl4oCdIHRoZWlyIHJlc3VsdHMgdG8gZGV0ZXJtaW5lIHRoZSByZXN1bHRhbnQgcmFuZG9tIGZvcmVzdCBtb2RlbC4gVGhvdWdoIHRoZSBtb2RlbCBpcyBub3QgYXMgaW50ZXJwcmV0YWJsZSBhcyBhIHNpbmdsZSB0cmVlIGFuZCBpdCBpcyBtb3JlIGRpZmZpY3VsdCB0byB1bmRlcnN0YW5kIHRoZSBzaWduaWZpY2FuY2Ugb2YgYSBzaW5nbGUgdmFyaWFibGUsIGl0IHdpbGwgcmVzdWx0IGluIGxvd2VyIG1pc2NsYXNzaWZpY2F0aW9uIHJhdGUuIFRoZSBudW1iZXIgb2YgdHJlZXMgaW4gdGhlIGZvcmVzdCBpcyBhIHBhcmFtZXRlciB0aGF0IG5lZWRzIHRvIGJlIHR1bmVkIGluIHRoaXMgbW9kZWwuIEFzIHRoZSBudW1iZXIgb2YgdHJlZXMgaW5jcmVhc2VzLCB0aGUgZXJyb3IgZGVjcmVhc2VzIGV4cG9uZW50aWFsbHksIHJlYWNoaW5nIGFuIGFzeW1wdG90ZSBvZiBlcnJvci4gDQoNClNWTSBtb2RlbHMgYXJlIHN1cGVydmlzZWQgbGVhcm5pbmcgbW9kZWxzIHRoYXQgdXNlIHRoZSBkYXRhIHBvaW50cyB0byBjcmVhdGUgYSBsaW5lIHRvIHNlcGFyYXRlIHRoZSBkYXRhLiBUaGlzIHNlcGFyYXRpb24gdGhlbiBkZWNpZGVzIHRoZSBiaW5hcnkgY2xhc3NpZmljYXRpb24gZm9yIGVhY2ggZGF0YSBwb2ludC4gV2hpbGUgdGhpcyBtb2RlbCBjYW4gYmUgaW5jcmVkaWJseSB2ZXJzYXRpbGUgYW5kIHJvYnVzdCBhZ2FpbnN0IG91dGxpZXJzIGFuZCBpbmFjY3VyYXRlIGRhdGEsIGl0IG1heSBub3QgYmUgYXMgYWNjdXJhdGUgaWYgdGhlcmUgaXMgbXVjaCBvdmVybGFwIGJldHdlZW4gdGhlIGRhdGEuIA0KDQpIZWxwZXIgRnVuY3Rpb246DQpgYGB7cn0NCkFsbEVycm9ycyA9IGZ1bmN0aW9uKGNvcnJlY3RSZXN1bHRzLA0KICAgICAgICAgICAgICAgICAgICAgcHJlZGljdGVkUmVzdWx0cywNCiAgICAgICAgICAgICAgICAgICAgIHNpemVUZXN0U2V0LCBudW1lcmljYWwgPSAwKSB7DQogIGlzV3JvbmcgPSAoY29ycmVjdFJlc3VsdHMgIT0gcHJlZGljdGVkUmVzdWx0cykNCiAgaXNSaWdodCA9IChjb3JyZWN0UmVzdWx0cyA9PSBwcmVkaWN0ZWRSZXN1bHRzKQ0KICBFcnJvcnMgPSBzdW0oY29ycmVjdFJlc3VsdHMgIT0gcHJlZGljdGVkUmVzdWx0cykNCiAgRXJyb3JSYXRlID0gRXJyb3JzIC8gc2l6ZVRlc3RTZXQNCiAgDQogIA0KICBpZiAobnVtZXJpY2FsID09IDApIHsNCiAgICBJc0MgPSAocHJlZGljdGVkUmVzdWx0cyA9PSAnQ09ORklSTUVEJykNCiAgICBJc0YgPSAocHJlZGljdGVkUmVzdWx0cyA9PSAnRkFMU0UgUE9TSVRJVkUnKQ0KICB9ZWxzZXsNCiAgICBJc0MgPSAocHJlZGljdGVkUmVzdWx0cyA9PSAxKQ0KICAgIElzRiA9IChwcmVkaWN0ZWRSZXN1bHRzID09IDApDQogIH0NCiAgDQogIEZhbHNlUG9zaXRpdmVzID0gc3VtKGlzV3JvbmcgJiBJc0MpDQogIEZhbHNlTmVnYXRpdmVzID0gc3VtKGlzV3JvbmcgJiBJc0YpDQogIFRydWVQb3NpdGl2ZXMgPSBzdW0oaXNSaWdodCAmIElzQykNCiAgVHJ1ZU5lZ2F0aXZlcyA9IHN1bShpc1JpZ2h0ICYgSXNGKQ0KICBGUF9SYXRlID0gKEZhbHNlUG9zaXRpdmVzIC8gKEZhbHNlUG9zaXRpdmVzICsgVHJ1ZU5lZ2F0aXZlcykpI2RlZmluZSBGUCByYXRlIGFzIEZQLyhGUCtUTikNCiAgRk5fUmF0ZSA9IChGYWxzZU5lZ2F0aXZlcyAvIChGYWxzZU5lZ2F0aXZlcyArIFRydWVQb3NpdGl2ZXMpKSNkZWZpbmUgRk4gcmF0ZSBhcyBGTi8oRk4rVFApDQogIHJldHVybihsaXN0KEVycm9yUmF0ZSwgRlBfUmF0ZSwgRk5fUmF0ZSkpDQp9DQpgYGANCg0KQ29kZSBmb3IgZGVjaXNpb24gdHJlZSwgcmFuZG9tIGZvcmVzdCwgYW5kIFNWTToNCmBgYHtyfQ0KDQoNCk51bUZvbGRzID0gNQ0KQ3JlYXRlRXJyb3JNYXRyaXggPSBmdW5jdGlvbihudW1Gb2xkcykgew0KICByZXR1cm4ocmVwKDAsIG51bUZvbGRzKSkNCn0NCg0KDQpkZWNUcmVlX0Vycm9yID0gQ3JlYXRlRXJyb3JNYXRyaXgoTnVtRm9sZHMpDQpkZWNUcmVlX0ZQID0gQ3JlYXRlRXJyb3JNYXRyaXgoTnVtRm9sZHMpDQpkZWNUcmVlX0ZOID0gQ3JlYXRlRXJyb3JNYXRyaXgoTnVtRm9sZHMpDQoNClJGX2Vycm9yID0gQ3JlYXRlRXJyb3JNYXRyaXgoTnVtRm9sZHMpDQpSRl9lcnJvcl9GUCA9IENyZWF0ZUVycm9yTWF0cml4KE51bUZvbGRzKQ0KUkZfZXJyb3JfRk4gPSBDcmVhdGVFcnJvck1hdHJpeChOdW1Gb2xkcykNClNWTV9lcnJvciA9IENyZWF0ZUVycm9yTWF0cml4KE51bUZvbGRzKQ0KU1ZNX2Vycm9yX0ZQID0gQ3JlYXRlRXJyb3JNYXRyaXgoTnVtRm9sZHMpDQpTVk1fZXJyb3JfRk4gPSBDcmVhdGVFcnJvck1hdHJpeChOdW1Gb2xkcykNCmZvciAoZm9sZCBpbiAxOk51bUZvbGRzKSB7DQogICNzZXQgdXAgay1jcm9zc2ZvbGQgdmFsaWRhdGlvbg0KICBzZXQuc2VlZChmb2xkKQ0KICANCiAgDQogICNzcGxpdCBkYXRhIGludG8gdGVzdGluZyBhbmQgdHJhaW5pbmcgc2V0cw0KICBudW1fc2FtcGxlcyA9IGRpbShsYWJlbGVkX2ZpbmFsKVsxXQ0KICBzYW1wbGluZy5yYXRlID0gMC44DQogIHRyYWluaW5nID0gc2FtcGxlKDE6bnVtX3NhbXBsZXMsIHNhbXBsaW5nLnJhdGUgKiBudW1fc2FtcGxlcywgcmVwbGFjZSA9IEZBTFNFKQ0KICB0cmFpbmluZ1NldCA9IHN1YnNldChsYWJlbGVkX2ZpbmFsW3RyYWluaW5nLCBdKQ0KICB0ZXN0aW5nID0gc2V0ZGlmZigxOm51bV9zYW1wbGVzLCB0cmFpbmluZykNCiAgdGVzdGluZ1NldCA9IHN1YnNldChsYWJlbGVkX2ZpbmFsW3Rlc3RpbmcsXSkNCiAgDQogIA0KICAjRGVjaXNpb24gdHJlZQ0KICBkZWNUcmVlTW9kZWwgPSBycGFydChrb2lfZGlzcG9zaXRpb24gfiAuLCBkYXRhID0gdHJhaW5pbmdTZXQpDQogIA0KICAjQXV0b21hdGljYWxseSBzZWxlY3QgdGhlIHN0b3BwaW5nIHBvaW50IHdoZXJlIGNwIG5vIGxvbmdlciBpbXByb3ZlcyBlcnJvciBieSAwLjA1DQogIGVycm9ycyA9IGRlY1RyZWVNb2RlbCRjcHRhYmxlWywgM10NCiAgZGVjVHJlZUNoYW5nZUVycm9yID0gYygwLCAwLCAwLCAwLCAwLCAwLCAwLCAwLCAwKQ0KICBmb3IgKGkgaW4gMTo4KSB7DQogICAgZGVjVHJlZUNoYW5nZUVycm9yW2ldID0gZXJyb3JzW2kgKyAxXSAtIGVycm9yc1tpXQ0KICB9DQogIGRlY1RyZWVDaGFuZ2VFcnJvcg0KICBmb3IgKGkgaW4gMTo5KSB7DQogICAgaWYgKGFicyhkZWNUcmVlQ2hhbmdlRXJyb3JbaV0pIDwgMC4wNSkgew0KICAgICAgc3RvcEluZGV4ID0gaQ0KICAgICAgYnJlYWsNCiAgICB9DQogIH0NCiAgY3BzID0gZGVjVHJlZU1vZGVsJGNwdGFibGVbLCAxXQ0KICBjcFN0b3AgPSBjcHNbc3RvcEluZGV4XQ0KICANCiAgDQogIHBydW5lZERlY1RyZWVNb2RlbCA9IHJwYXJ0OjpwcnVuZShkZWNUcmVlTW9kZWwsIGNwID0gY3BTdG9wKSAjUHJ1bmUgZGVjaXNpb24gdHJlZQ0KICBkZWNUcmVlUHJlZGljdGlvbnMgPSBwcmVkaWN0KHBydW5lZERlY1RyZWVNb2RlbCwgdGVzdGluZ1NldCwgdHlwZSA9ICJjbGFzcyIpICNtYWtlIHByZWRpY3Rpb25zDQogICNEZXRlcm1pbmUgZGVjaXNpb24gdHJlZSBlcnJvcg0KICBzaXplVGVzdFNldCA9IGRpbSh0ZXN0aW5nU2V0KVsxXQ0KICBkZWNUcmVlTW9kZWxfZXJyb3JzID0gQWxsRXJyb3JzKHRlc3RpbmdTZXQka29pX2Rpc3Bvc2l0aW9uLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlY1RyZWVQcmVkaWN0aW9ucywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaXplVGVzdFNldCkNCiAgZGVjVHJlZV9FcnJvcltmb2xkXSA9IGRlY1RyZWVNb2RlbF9lcnJvcnNbWzFdXQ0KICBkZWNUcmVlX0ZQW2ZvbGRdID0gZGVjVHJlZU1vZGVsX2Vycm9yc1tbMl1dDQogIGRlY1RyZWVfRk5bZm9sZF0gPSBkZWNUcmVlTW9kZWxfZXJyb3JzW1szXV0NCiAgI1JhbmRvbSBGb3Jlc3QNCiAgUmFuZEZvcmVzdE1vZGVsID0gcmFuZG9tRm9yZXN0KGtvaV9kaXNwb3NpdGlvbiB+IC4sIGRhdGEgPSB0cmFpbmluZ1NldCwgbnRyZWVzID0gMjAwKSN2aXN1YWwgaW5zcGVjdGlvbiBnaXZlcyB0aGlzIGFzIGEgZ29vZCBudW1iZXIgZm9yIHRyZWVzDQogIA0KICBwcmVkaWN0ZWRMYWJlbHMgPSBwcmVkaWN0KFJhbmRGb3Jlc3RNb2RlbCwgdGVzdGluZ1NldCkNCiAgDQogICNEZXRlcm1pbmUgUmFuZG9tIEZvcmVzdCBFcnJvcg0KICBzaXplVGVzdFNldCA9IGRpbSh0ZXN0aW5nU2V0KVsxXQ0KICBSRl9FcnJvcnMgPSBBbGxFcnJvcnModGVzdGluZ1NldCRrb2lfZGlzcG9zaXRpb24sIHByZWRpY3RlZExhYmVscywgc2l6ZVRlc3RTZXQpDQogIA0KDQogIFJGX2Vycm9yW2ZvbGRdID0gUkZfRXJyb3JzW1sxXV0NCiAgUkZfZXJyb3JfRlBbZm9sZF0gPSBSRl9FcnJvcnNbWzJdXQ0KICBSRl9lcnJvcl9GTltmb2xkXSA9IFJGX0Vycm9yc1tbM11dDQogIA0KICAjU1ZNIE1vZGVsDQogIHN2bU1vZGVsID0gc3ZtKGtvaV9kaXNwb3NpdGlvbiB+IC4sIGRhdGEgPSB0cmFpbmluZ1NldCwga2VybmVsID0gImxpbmVhciIpDQogIHByZWRpY3RlZGxhYmVsc1NWTSA9IHByZWRpY3Qoc3ZtTW9kZWwsIHRlc3RpbmdTZXQpDQogICNEZXRlcm1pbmUgU1ZNIGVycm9yDQogIFNWTV9FcnJvcnMgPSBBbGxFcnJvcnModGVzdGluZ1NldCRrb2lfZGlzcG9zaXRpb24sDQogICAgICAgICAgICAgICAgICAgICAgICAgcHJlZGljdGVkbGFiZWxzU1ZNLA0KICAgICAgICAgICAgICAgICAgICAgICAgIHNpemVUZXN0U2V0KQ0KICANCiAgZXJyb3JTVk0gPSBzdW0ocHJlZGljdGVkbGFiZWxzU1ZNICE9IHRlc3RpbmdTZXQka29pX2Rpc3Bvc2l0aW9uKQ0KICBtaXNjbGFzc2lmaWNhdGlvbl9yYXRlU1ZNID0gZXJyb3JTVk0gLyBzaXplVGVzdFNldA0KICBTVk1fZXJyb3JbZm9sZF0gPSBTVk1fRXJyb3JzW1sxXV0NCiAgU1ZNX2Vycm9yX0ZQW2ZvbGRdID0gU1ZNX0Vycm9yc1tbMl1dDQogIFNWTV9lcnJvcl9GTltmb2xkXSA9IFNWTV9FcnJvcnNbWzNdXQ0KfQ0KI1Rha2UgYXZlcmFnZSBvZiBlcnJvcnMgZnJvbSBlYWNoIGZvbGQgdG8gZGV0ZXJtaW5lIGF2ZXJhZ2UgZXJyb3IgZm9yIGVhY2ggbW9kZWwNCg0KQXZnRXJyb3JEVCA9IG1lYW4oZGVjVHJlZV9FcnJvcikNCkF2Z0ZQX0RUID0gbWVhbihkZWNUcmVlX0ZQKQ0KQXZnRk5fRFQgPSBtZWFuKGRlY1RyZWVfRk4pDQpBdmdFcnJvclJGID0gbWVhbihSRl9lcnJvcikNCkF2Z0ZQX1JGID0gbWVhbihSRl9lcnJvcl9GUCkNCkF2Z0ZOX1JGID0gbWVhbihSRl9lcnJvcl9GTikNCkF2Z0Vycm9yU1ZNID0gbWVhbihTVk1fZXJyb3IpDQpBdmdGUF9TVk0gPSBtZWFuKFNWTV9lcnJvcl9GUCkNCkF2Z0ZOX1NWTSA9IG1lYW4oU1ZNX2Vycm9yX0ZOKQ0KDQpwYXN0ZSgiRFQgRXJyb3I6ICIsIHJvdW5kKDEwMCAqIEF2Z0Vycm9yRFQsIDIpLCAiJSIsIHNlcCA9ICIiKQ0KcGFzdGUoIlJGIEVycm9yOiAiLCByb3VuZCgxMDAgKiBBdmdFcnJvclJGLCAyKSwgIiUiLCBzZXAgPSAiIikNCnBhc3RlKCJTVk0gRXJyb3I6ICIsIHJvdW5kKDEwMCAqIEF2Z0Vycm9yU1ZNLCAyKSwgIiUiLCBzZXAgPSAiIikNCmBgYA0KQWxsIHRocmVlIG1vZGVscyBnaXZlIGxvdyBtaXNjbGFzc2lmaWNhdGlvbiByYXRlcyA8MTIlLCBidXQgUmFuZG9tIEZvcmVzdCBnaXZlcyB0aGUgYmVzdCBtaXNjbGFzc2lmaWNhdGlvbiByYXRlIGF0IG9ubHkgNi43NCUuDQoNCiMjIyAzLjEuMikgWEdCb29zdA0KDQpYR0Jvb3N0IHN0YW5kcyBmb3Ig4oCcZXh0cmVtZSBncmFkaWVudCBib29zdGluZ+KAnSBhbmQgaXMgYW4gb3Blbi1zb3VyY2UgdHJlZSBsZWFybmluZyBhbGdvcml0aG0gc2ltaWxhciB0byByYW5kb20gZm9yZXN0cyB0aGF0IGlzIGFsc28gd2lkZWx5IHVzZWQgaW4gaW5kdXN0cnkuIFRoaXMgbW9kZWwgc2Vla3MgdG8gbWluaW1pemUgYW4gb2JqZWN0aXZlIGZ1bmN0aW9uIHJlcHJlc2VudGluZyBtb2RlbCBjb21wbGV4aXR5IGFuZCBsb3NzIChlcnJvciksIHVzaW5nIGEgZ3JhZGllbnQgZGVzY2VudCBhbGdvcml0aG0gdG8gbWluaW1pemUgbG9zcyB3aGVuIGFkZGluZyBuZXcgbW9kZWxzLiBUaGlzIGlzIGtub3duIGFzIHRyZWUgYm9vc3Rpbmc7IHJhbmRvbSBmb3Jlc3QgbW9kZWxzIGRpZmZlciBiZWNhdXNlIHRoZXkgdXNlIGEgdHJlZSBiYWdnaW5nIGFsZ29yaXRobSwgcG9zc2libHkgbGVhZGluZyB0byBkaWZmZXJlbnQgbW9kZWwgYWNjdXJhY2llcy4gDQoNClhHQm9vc3Qgb3V0cHV0cyBhIHByb2JhYmlsaXR5IGJldHdlZW4gMCBhbmQgMSwgcmF0aGVyIHRoYW4gYSBiaW5hcnkgY2xhc3NpZmljYXRpb24uIEZvciB0aGlzIHJlYXNvbiwgaXQgaXMgbmVjZXNzYXJ5IHRvIGRldGVybWluZSB0aGUgInRocmVzaG9sZCIgd2hlcmUgYSB2YWx1ZSBzdG9wcyBiZWluZyBhIEZBTFNFIFBPU0lUSVZFLCBhbmQgc3RhcnQgYmVpbmcgYSBDT05GSVJNRUQuIFF1ZXN0aW9uaW5nIHRoZSBjbGFzc2lmaWNhdGlvbiB0aHJlc2hvbGQgd2hpY2ggaXMgYnVpbHQgaW50byBvdXIgbW9kZWxzIGNhbiBoZWxwIHVzIGRldmVsb3AgbW9yZSBhY2N1cmF0ZSBtb2RlbHMuIEZvciBleGFtcGxlLCBhcmJpdHJhcmlseSBhc3N1bWluZyB0aGF0IHRoZSB0aHJlc2hvbGQgZm9yIGNsYXNzaWZ5aW5nIGJhc2VkIG9uIG91ciBYR0Jvb3N0IG1vZGVsIHdhcyBleGFjdGx5IDAuNSBjb3VsZCBoYXZlIHJlc3VsdGVkIGluIGEgaGlnaGVyIG1pc2NsYXNzaWZpY2F0aW9uIHJhdGUgaWYgd2Ugd2VyZSB0byBjb25zaWRlciBhbGwgcG9zc2libGUgdGhyZXNob2xkcy4gQXMgYSByZXN1bHQsIHdlIGNvbmR1Y3RlZCB0aHJlc2hvbGQgYW5hbHlzaXMgb24gYWxsIG91ciBtb2RlbHMgdGhhdCBvdXRwdXQgcHJvYmFiaWxpdGllcyBieSBsb29waW5nIHRocm91Z2ggYWxsIGNsYXNzaWZpY2F0aW9uIHRocmVzaG9sZHMgYXQgMC4xIGluY3JlbWVudHMsIHBsb3R0ZWQgdGhlbSBhZ2FpbnN0IHRoZWlyIHJlc3BlY3RpdmUgbWlzY2xhc3NpZmljYXRpb24gcmF0ZXMsIGFuZCB0b29rIHRoZSBtaW5pbXVtIGFzIHRoZSBvcHRpbWFsLiBUaGUgZmlyc3QgbW9kZWwgdGhhdCB3ZSBoYXZlIGRvbmUgdGhpcyBmb3IgaXMgWEdCb29zdC4gIA0KDQpgYGB7cn0NCnhnYi5zZXQuY29uZmlnKHZlcmJvc2l0eSA9IDApDQp0aHJlc2hvbGQgPSAwLjENClhHQm9vc3RfZXJyb3JfdGhyZXNob2xkcyA9IHJlcCgwLCAxMCkNClhHQm9vc3RfRlBfdGhyZXNob2xkcyA9IHJlcCgwLDEwKQ0KWEdCb29zdF9GTl90aHJlc2hvbGRzID0gcmVwKDAsMTApDQppbmRleCA9IDENCg0Kd2hpbGUgKHRocmVzaG9sZCA8IDEpIHsNCiAgI2xvb3AgdGhhdCByZXJ1bnMgdGhlIGFsZ29yaXRobSB3aXRoIGluY3JlbWVudHMgb2YgMC4xIGluIHRoZSB0aHJlc2hvbGQNCiAgWEdCX2Vycm9yID0gQ3JlYXRlRXJyb3JNYXRyaXgoTnVtRm9sZHMpDQogIFhHQl9GTnMgPSBDcmVhdGVFcnJvck1hdHJpeChOdW1Gb2xkcykNCiAgWEdCX0ZQcyA9IENyZWF0ZUVycm9yTWF0cml4KE51bUZvbGRzKQ0KICBmb3IgKGZvbGQgaW4gMTpOdW1Gb2xkcykgew0KICAgICNrIGNyb3NzLWZvbGQgdmFsaWRhdGlvbg0KICAgIHNldC5zZWVkKGZvbGQpDQogICAgDQogICAgbnVtX3NhbXBsZXMgPSBkaW0obGFiZWxlZF9maW5hbClbMV0NCiAgICANCiAgICAjY3JlYXRlIHRlc3RpbmcgYW5kIHRyYWluaW5nIHNldA0KICAgIHNhbXBsaW5nLnJhdGUgPSAwLjgNCiAgICB0cmFpbmluZyA9IHNhbXBsZSgxOm51bV9zYW1wbGVzLCBzYW1wbGluZy5yYXRlICogbnVtX3NhbXBsZXMsIHJlcGxhY2UgPSBGQUxTRSkNCiAgICB0cmFpbmluZ1NldCA9IHN1YnNldChsYWJlbGVkX2ZpbmFsW3RyYWluaW5nLF0pDQogICAgdGVzdGluZyA9IHNldGRpZmYoMTpudW1fc2FtcGxlcywgdHJhaW5pbmcpDQogICAgdGVzdGluZ1NldCA9IHN1YnNldChsYWJlbGVkX2ZpbmFsW3Rlc3RpbmcsIF0pDQogICAgDQogICAgI1hHQm9vc3QgbW9kZWwNCiAgICB4Z1RyYWluID0gZGF0YS5tYXRyaXgodHJhaW5pbmdTZXQpDQogICAgeGdUcmFpblssIDFdID0gaWZlbHNlKHhnVHJhaW5bLCAxXSA9PSAyLCAxLCAwKQ0KICAgDQogICAgeGdCb29zdE1vZGVsID0geGdib29zdCgNCiAgICAgIGRhdGEgPSB4Z1RyYWluWywgMjozNl0sDQogICAgICBsYWJlbCA9IHhnVHJhaW5bLCAxXSwNCiAgICAgIG1heC5kZXB0aCA9IDYsDQogICAgICBldGEgPSAuMjIsDQogICAgICBucm91bmRzID0gMTAwLA0KICAgICAgdmVyYm9zZSA9IDAsDQogICAgICBvYmplY3RpdmUgPSAiYmluYXJ5OmxvZ2lzdGljIiwNCiAgICAgIGV2YWxfbWV0cmljPSJlcnJvciINCiAgICApDQogICAgeGdUZXN0ID0gZGF0YS5tYXRyaXgodGVzdGluZ1NldCkNCiAgICB4Z1Rlc3RbLCAxXSA9IGlmZWxzZSh4Z1Rlc3RbLCAxXSA9PSAyLCAxLCAwKQ0KICAgIA0KICAgIA0KICAgIA0KICAgICNtYWtlIHByZWRpY3Rpb25zDQogICAgQm9vc3RQcmVkaWN0aW9ucyA9IHByZWRpY3QoeGdCb29zdE1vZGVsLCBkYXRhLm1hdHJpeCh0ZXN0aW5nU2V0KVssIDI6MzZdKQ0KICAgIA0KICAgIA0KICAgIA0KICAgIA0KICAgIEJvb3N0UHJlZGljdGlvbnNSb3VuZGVkID0gaWZlbHNlKEJvb3N0UHJlZGljdGlvbnMgPiB0aHJlc2hvbGQsIDEsIDApICNjb252ZXJ0IHByb2JhYmlsaXRpZXMgdG8gb3VwdXRzIG9mIDEgb3IgMCBiYXNlZCBvbiB3aGV0aGVyIHRoZXkgYXJlIGdyZWF0ZXIgdGhhbiB0aGUgdGhyZXNob2xkIC0gdGhpcyBpcyB3aGVyZSBwYXJhbWV0ZXIgdHVuaW5nIG9jY3Vycy4NCiAgICBCb29zdEVycm9ycyA9IEFsbEVycm9ycyh4Z1Rlc3RbLCAxXSxCb29zdFByZWRpY3Rpb25zUm91bmRlZCxkaW0oeGdUZXN0KVsxXSwxKQ0KICAgIFhHQl9lcnJvcltmb2xkXSA9IEJvb3N0RXJyb3JzW1sxXV0NCiAgICBYR0JfRlBzID0gQm9vc3RFcnJvcnNbWzJdXQ0KICAgIFhHQl9GTnMgPSBCb29zdEVycm9yc1tbM11dDQogICANCiAgfQ0KICBYR0Jvb3N0X2Vycm9yX3RocmVzaG9sZHNbaW5kZXhdID0gbWVhbihYR0JfZXJyb3IpI2F2ZXJhZ2UgdGhlIGVycm9yIGZyb20gYWxsIGZvbGRzDQogIFhHQm9vc3RfRlBfdGhyZXNob2xkc1tpbmRleF0gPSBtZWFuKFhHQl9GUHMpDQogIFhHQm9vc3RfRk5fdGhyZXNob2xkc1tpbmRleF0gPSBtZWFuKFhHQl9GTnMpDQogIA0KICBpbmRleCA9IGluZGV4ICsgMQ0KICB0aHJlc2hvbGQgPSB0aHJlc2hvbGQgKyAuMQ0KfQ0KeGdiUGxvdCA9IHhnYi5wbG90LnRyZWUobW9kZWwgPSB4Z0Jvb3N0TW9kZWwsDQogICAgICAgICAgICAgICAgICAgICAgICB0cmVlcyA9IDEsDQogICAgICAgICAgICAgICAgICAgICAgICByZW5kZXIgPSBUUlVFKQ0KeGdiUGxvdCAjcGxvdCBhbiBleGFtcGxlIHRyZWUNCg0KDQojRGV0ZXJtaW5lIGNvcnJlY3QgdGhyZXNob2xkIHZhbHVlDQpYR0Jvb3N0X2Vycm9yX3RocmVzaG9sZHMjc2hvd3MgdGhlIGF2ZXJhZ2UgZXJyb3IgYXQgZWFjaCB0aHJlc2hvbGQNCm9yZGVyKFhHQm9vc3RfZXJyb3JfdGhyZXNob2xkcykgI0xvd2VzdCBlcnJvciBpcyB0aHJlc2hvbGQgPSAuNA0KQXZnRXJyb3JYR0IgPSBYR0Jvb3N0X2Vycm9yX3RocmVzaG9sZHNbKG9yZGVyKFhHQm9vc3RfZXJyb3JfdGhyZXNob2xkcylbMV0pXSAjY2hvc2UgdGhlIHRocmVzaG9sZCB3aXRoIHRoZSBsb3dlc3QgZXJyb3IgYXMgdGhlIG9uZSB3ZSB1c2UNCkF2Z0ZQX1hHQiA9IFhHQm9vc3RfRlBfdGhyZXNob2xkc1sob3JkZXIoWEdCb29zdF9lcnJvcl90aHJlc2hvbGRzKVsxXSldDQpBdmdGTl9YR0IgPSBYR0Jvb3N0X0ZOX3RocmVzaG9sZHNbKG9yZGVyKFhHQm9vc3RfZXJyb3JfdGhyZXNob2xkcylbMV0pXQ0KcGFzdGUoIlhHQm9vc3QgRXJyb3I6ICIsIHJvdW5kKEF2Z0Vycm9yWEdCKjEwMCwyKSwgIiUiLCBzZXAgPSAiIikNCiNwbG90IHRoZSBlcnJvciB0aHJlc2hvbGRzDQpwbG90KA0KICB4ID0gMToxMCAvIDEwLA0KICB5ID0gWEdCb29zdF9lcnJvcl90aHJlc2hvbGRzLA0KICBtYWluID0gIkF2ZyBFcnJvciBvdmVyIGRpZmZlcmVudCB0aHJlc2hvbGRzIGZvciBYR0Jvb3N0IE1vZGVsIiwNCiAgeGxhYiA9ICJDdXRvZmYgVGhyZXNob2xkIiwNCiAgeWxhYiA9ICJNaXNjbGFzc2lmaWNhdGlvbiBSYXRlIg0KKQ0KbGluZXMoeCA9IDE6MTAgLyAxMCwgeSA9IFhHQm9vc3RfZXJyb3JfdGhyZXNob2xkcykNCmBgYA0KWEdCb29zdCBnaXZlcyBhbiBlcnJvciByYXRlIG9mIGByIHJvdW5kKEF2Z0Vycm9yWEdCKjEwMCwyKWAlLiBUaGlzIGlzIHNpZ25pZmljYW50bHkgYmV0dGVyIHRoYW4gYW55IG1vZGVsIHJ1biBzbyBmYXIuICBUaGUgdGhyZXNob2xkIGFuYWx5c2lzIGdyYXBoIGFib3ZlIHNob3dzIHRoYXQgdGhlIGJlc3QgbWlzY2xhc3NpZmljYXRpb24gcmF0ZSBmb3IgWEdCb29zdCBpcyBhdCB0aGUgdGhyZXNob2xkID0gLmByIChvcmRlcihYR0Jvb3N0X2Vycm9yX3RocmVzaG9sZHMpWzFdKWAuIA0KIA0KICAgDQojIyMgMy4xLjUpIExvZ2lzdGljIFJlZ3Jlc3Npb24NCg0KTG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbHMgaXMgYSBzdXBlcnZpc2VkIGNsYXNzaWZpY2F0aW9uIGFsZ29yaXRobSB0aGF0IGJ1aWxkcyBhIHJlZ3Jlc3Npb24gbW9kZWwgdG8gcHJlZGljdCB0aGUgY2xhc3NpZmljYXRpb24gYnkgYXNzaWduaW5nIGRhdGEgZW50cmllcyB0byBiaW5hcnkgdmFsdWVzLCBiYXNlZCBvbiB0aGUgU2lnbW9pZCBmdW5jdGlvbi4gV2hlbiBwZXJmb3JtaW5nIGxvZ2lzdGljIHJlZ3Jlc3Npb24sIGl0IGlzIGltcG9ydGFudCB0byBjb25zaWRlciB0aGUgcHJvYmxlbXMgdGhhdCBhcmlzZSBmcm9tIG11bHRpY29sbGluZWFyaXR5IHdoaWNoIGNhbiBjYXVzZSB1bnN0YWJsZSBlc3RpbWF0ZXMgYW5kIGluYWNjdXJhY3kuIEZvciB0aGlzIHJlYXNvbiwgd2UgZGVjaWRlZCB0byBmaXJzdCByZW1vdmUgYWxsIG1ham9yIG11bHRpY29sbGluZWFyaXR5IGZyb20gdGhlIG1vZGVsLiANCg0KYGBge3J9DQojQ2hlY2sgZm9yIG11bHRpY29sbGluZWFyaXR5DQpjb3JyRnJhbWUgPSBkYXRhLmZyYW1lKGNvcihsYWJlbGVkX2ZpbmFsWywgMjozNl0pKQ0KY29ycnBsb3QoY29yKGxhYmVsZWRfZmluYWxbLCAyOjM2XSkpICNtYW55IG11bHRpY29sbGluZWFyIHZhcmlhYmxlcy4gRGVmaW5pbmUgY29sbGluZWFyaXR5IGFzIGNvcnJlbGF0aW9uID4uNA0KDQoNCkdMTV9EYXRhID0gZGF0YS5mcmFtZShsYWJlbGVkX2ZpbmFsJGtvaV9kaXNwb3NpdGlvbikNCg0KDQojYWxsIGVycjJzIGFyZSBjb2xsaW5lYXIgd2l0aCBlcnIxcy4gUmVtb3ZpbmcgZXJyMXMNCnZhcmlhYmxlX2NvdW50ZXIgPSAyDQp2YXJpYWJsZS5uYW1lcyA9IGNvbG5hbWVzKGxhYmVsZWRfZmluYWwpDQpmb3IgKGkgaW4gMjpsZW5ndGgobGFiZWxlZF9maW5hbCkpIHsNCiAgdmFyaWFibGUubmFtZXNbaV0NCiAgZXJyb3IxID0gZ3JlcGwoIl9lcnIxIiwgdmFyaWFibGUubmFtZXNbaV0sIGZpeGVkID0gVFJVRSkNCiAgaWYgKGVycm9yMSA9PSBGQUxTRSkgew0KICAgIEdMTV9EYXRhWywgdmFyaWFibGVfY291bnRlcl0gPSBsYWJlbGVkX2ZpbmFsWywgaV0NCiAgICB2YXJpYWJsZV9jb3VudGVyID0gdmFyaWFibGVfY291bnRlciArIDENCiAgfSBlbHNlew0KICAgIHZhcmlhYmxlLm5hbWVzW2ldID0gTkENCiAgfQ0KfQ0KdmFyaWFibGUubmFtZXMgPSBuYS5vbWl0KHZhcmlhYmxlLm5hbWVzKQ0KY29sbmFtZXMoR0xNX0RhdGEpID0gdmFyaWFibGUubmFtZXMNCkdMTV9EYXRhDQoNCmNvcnJGcmFtZTIgPSBkYXRhLmZyYW1lKGNvcihHTE1fRGF0YVssIDI6ZGltKEdMTV9EYXRhKVsyXV0pKQ0KY29ycnBsb3QoY29yKEdMTV9EYXRhWywgMjpkaW0oR0xNX0RhdGEpWzJdXSkpICNNYW55IGNvcnJlbGF0ZWQgdmFyaWFibGVzIHJlbWFpbg0KDQoNCiNLT0lfcGVyaW9kIGlzIGNvbGxpbmVhciB3aXRoIGtvaV90aW1lMGINCkdMTV9EYXRhID0gc3Vic2V0KEdMTV9EYXRhLCBzZWxlY3QgPSAtYyhrb2lfdGltZTBiaykpDQoja29pX3BlcmlvZF9lcnIgaXMgY29sbGluZWFyIHdpdGgga29pX3RpbWUwYmsgZXJyb3INCkdMTV9EYXRhID0gc3Vic2V0KEdMTV9EYXRhLCBzZWxlY3QgPSAtYyhrb2lfdGltZTBia19lcnIyKSkNCiNLb2lfcGVyaW9kIGlzIGNvbGxpbmVhciB3aXRoIGtvaV9wZXJpb2RfZXJyMg0KR0xNX0RhdGEgPSBzdWJzZXQoR0xNX0RhdGEsIHNlbGVjdCA9IC1jKGtvaV9wZXJpb2RfZXJyMikpDQoja29pX2ltcGFjdCBpcyBjb2xsaW5lYXIgd2l0aCBrb2lfaW1wYWN0X2VycjINCkdMTV9EYXRhID0gc3Vic2V0KEdMTV9EYXRhLCBzZWxlY3QgPSAtYyhrb2lfaW1wYWN0X2VycjIpKQ0KI2tvaV9kZXB0aCBpcyBjb2xsaW5lYXIgd2l0aCBrb2lfbW9kZWxfc25yDQpHTE1fRGF0YSA9IHN1YnNldChHTE1fRGF0YSwgc2VsZWN0ID0gLWMoa29pX21vZGVsX3NucikpDQoja29pIGltcGFjdCBpcyBjb2xsaW5lYXIgd2l0aCBrb2lfcHJhZCBhbmQga29pX3ByYWRfZXJyDQpHTE1fRGF0YSA9IHN1YnNldChHTE1fRGF0YSwgc2VsZWN0ID0gLWMoa29pX3ByYWQsIGtvaV9wcmFkX2VycjIpKQ0KI2tvaV90ZXEgaXMgY29sbGluZWFyIHdpdGggS09JX2luc29sLCBLb2lfaW5zb2xfZXJyLCBrb2lfc2xvZ2csIGtvaV9zcmFkLCBhbmQga29pX3NyYWRfZXJyDQpHTE1fRGF0YSA9IHN1YnNldChHTE1fRGF0YSwNCiAgICAgICAgICAgICAgICAgIHNlbGVjdCA9IC1jKGtvaV9pbnNvbCwga29pX2luc29sX2VycjIsIGtvaV9zbG9nZywga29pX3NyYWQsIGtvaV9zcmFkX2VycjIpKQ0KI2tvaV9zdGVmZiBpcyBjb2xsaW5lYXIgd2l0aCBrb2lfc3RlZmZfZXJyMiBhbmQga29pX3Nsb2dnX2VycjINCkdMTV9EYXRhID0gc3Vic2V0KEdMTV9EYXRhLCBzZWxlY3QgPSAtYyhrb2lfc2xvZ2dfZXJyMiwga29pX3N0ZWZmX2VycjIpKQ0KY29ycnBsb3QoY29yKEdMTV9EYXRhWywgMjpkaW0oR0xNX0RhdGEpWzJdXSkpDQojQWxsIG1ham9yIG11bHRpY29sbGluZWFyaXR5IGhhcyBub3cgYmVlbiByZW1vdmVkDQoNCg0KYGBgDQpTaW1pbGFybHkgdG8gWEdCb29zdCwgdGhlIGN1dG9mZiB0aHJlc2hvbGQgIGZvciBwcmVkaWN0aW9uIG11c3QgYmUgdHVuZWQuIE91ciBncm91cCBkZWNpZGVkIHRvIHJ1biBtdWx0aXBsZSB0ZXN0cyByYW5naW5nIGZyb20gMC4xIHRvIDEgdG8gZGV0ZXJtaW5lIHRoYXQgdGhlIGlkZWFsIHRocmVzaG9sZCB2YWx1ZSBvZiAwLjUgc2hvdWxkIGJlIHVzZWQgYXMgdGhhdCBjb3JyZXNwb25kZWQgd2l0aCB0aGUgbG93ZXN0IGF2ZXJhZ2UgZXJyb3IuIA0KYGBge3J9DQp0aHJlc2hvbGQgPSAwLjENCkdMTV9lcnJvciA9IENyZWF0ZUVycm9yTWF0cml4KE51bUZvbGRzKQ0KR0xNX2Vycm9yX3RocmVzaG9sZHMgPSByZXAoMCwgMTApDQpHTE1fRlBfdGhyZXNob2xkcyA9IHJlcCgwLCAxMCkNCkdMTV9GTl90aHJlc2hvbGRzID0gcmVwKDAsIDEwKQ0KaW5kZXggPSAxDQp3aGlsZSAodGhyZXNob2xkIDwgMSkgew0KICAjbG9vcCBmb3IgdGhyZXNob2xkIGFuYWx5c2lzDQogIEdMTV9lcnJvciA9IENyZWF0ZUVycm9yTWF0cml4KE51bUZvbGRzKQ0KICBHTE1fRlAgPSBDcmVhdGVFcnJvck1hdHJpeChOdW1Gb2xkcykNCiAgR0xNX0ZOID0gQ3JlYXRlRXJyb3JNYXRyaXgoTnVtRm9sZHMpDQogIGZvciAoZm9sZCBpbiAxOjUpIHsNCiAgICAjSy1jcm9zcyBmb2xkIHZhbGlkYXRpb24NCiAgICANCiAgICAjdHJhaW5pbmcvdGVzdGluZyBzZXQNCiAgICBzZXQuc2VlZChmb2xkKQ0KICAgIG51bV9zYW1wbGVzID0gZGltKEdMTV9EYXRhKVsxXQ0KICAgIHNhbXBsaW5nLnJhdGUgPSAwLjgNCiAgICB0cmFpbmluZyA9IHNhbXBsZSgxOm51bV9zYW1wbGVzLCBzYW1wbGluZy5yYXRlICogbnVtX3NhbXBsZXMsIHJlcGxhY2UgPSBGQUxTRSkNCiAgICB0cmFpbmluZ1NldCA9IHN1YnNldChHTE1fRGF0YVt0cmFpbmluZyxdKQ0KICAgIHRlc3RpbmcgPSBzZXRkaWZmKDE6bnVtX3NhbXBsZXMsIHRyYWluaW5nKQ0KICAgIHRlc3RpbmdTZXQgPSBzdWJzZXQoR0xNX0RhdGFbdGVzdGluZywgXSkNCiAgICBkZWZhdWx0VyA9IGdldE9wdGlvbigid2FybiIpDQogICAgb3B0aW9ucyh3YXJuID0gLTEpDQogICAgI3NldCB1cCBtb2RlbA0KICAgIExvZ2lzdGljUmVnID0gZ2xtKGtvaV9kaXNwb3NpdGlvbiB+IC4sDQogICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluaW5nU2V0LA0KICAgICAgICAgICAgICAgICAgICAgIGZhbWlseSA9IGJpbm9taWFsKGxvZ2l0KSkNCiAgICANCiAgICANCiAgICBvcHRpb25zKHdhcm4gPSBkZWZhdWx0VykNCiAgICBwcmVkaWN0aW9ucyA9IHByZWRpY3QoTG9naXN0aWNSZWcsIHRlc3RpbmdTZXQsIHR5cGUgPSAicmVzcG9uc2UiKQ0KICAgIHByZWRpY3RlZExhYmVscyA9IHJlcCgwLCBzaXplVGVzdFNldCkNCiAgICBwcmVkaWN0ZWRMYWJlbHMgPSBpZmVsc2UocHJlZGljdGlvbnMgPiB0aHJlc2hvbGQsICdGQUxTRSBQT1NJVElWRScsICdDT05GSVJNRUQnKSAjdGhpcyBwYXJhbWV0ZXIgaXMgdHVuZWQNCiAgICANCiAgICBHTE1FcnJvcnMgPSBBbGxFcnJvcnModGVzdGluZ1NldCRrb2lfZGlzcG9zaXRpb24sIHByZWRpY3RlZExhYmVscywgc2l6ZVRlc3RTZXQsIDApDQogICAgI2RldGVybWluZSBlcnJvcg0KICAgICMgZXJyb3IgPSBzdW0ocHJlZGljdGVkTGFiZWxzICE9IHRlc3RpbmdTZXQka29pX2Rpc3Bvc2l0aW9uKQ0KICAgICNtaXNjbGFzc2lmaWNhdGlvblJhdGVMUiA9IGVycm9yIC8gc2l6ZVRlc3RTZXQNCiAgICAjR0xNX2Vycm9yW2ZvbGRdID0gbWlzY2xhc3NpZmljYXRpb25SYXRlTFINCiAgICBHTE1fZXJyb3JbZm9sZF0gPSBHTE1FcnJvcnNbWzFdXQ0KICAgIEdMTV9GUFtmb2xkXSA9IEdMTUVycm9yc1tbMl1dDQogICAgR0xNX0ZOW2ZvbGRdID0gR0xNRXJyb3JzW1szXV0NCiAgICANCiAgfQ0KICANCiAgR0xNX2Vycm9yX3RocmVzaG9sZHNbaW5kZXhdID0gbWVhbihHTE1fZXJyb3IpDQogIEdMTV9GUF90aHJlc2hvbGRzW2luZGV4XSA9IG1lYW4oR0xNX0ZQKQ0KICBHTE1fRk5fdGhyZXNob2xkc1tpbmRleF0gPSBtZWFuKEdMTV9GTikNCiAgaW5kZXggPSBpbmRleCArIDENCiAgdGhyZXNob2xkID0gdGhyZXNob2xkICsgLjENCn0NCm9yZGVyKEdMTV9lcnJvcl90aHJlc2hvbGRzKSAjTG93ZXN0IGVycm9yIGlzIHRocmVzaG9sZCA9IC41DQpBdmdFcnJvckdMTSA9IEdMTV9lcnJvcl90aHJlc2hvbGRzW29yZGVyKEdMTV9lcnJvcl90aHJlc2hvbGRzKVsxXV0NCkF2Z0ZQX0dMTSA9IEdMTV9GUF90aHJlc2hvbGRzW29yZGVyKEdMTV9lcnJvcl90aHJlc2hvbGRzKVsxXV0NCkF2Z0ZOX0dMTSA9IEdMTV9GTl90aHJlc2hvbGRzW29yZGVyKEdMTV9lcnJvcl90aHJlc2hvbGRzKVsxXV0NCg0KcGFzdGUoIkxvZ2lzdGljIFJlZ3Jlc3Npb24gRXJyb3I6ICIsIHJvdW5kKEF2Z0Vycm9yR0xNKjEwMCwyKSwiJSIsc2VwPSIiKQ0KcGxvdCgNCiAgeCA9IDE6MTAgLyAxMCwNCiAgeSA9IEdMTV9lcnJvcl90aHJlc2hvbGRzLA0KICBtYWluID0gIkF2ZyBFcnJvciBvdmVyIGRpZmZlcmVudCB0aHJlc2hvbGRzIGZvciBMb2dpc3RpYyBSZWdyZXNzaW9uIE1vZGVsIiwNCiAgeGxhYiA9ICJDdXRvZmYgVGhyZXNob2xkIiwNCiAgeWxhYiA9ICJNaXNjbGFzc2lmaWNhdGlvbiBSYXRlIg0KKQ0KbGluZXMoeCA9IDE6MTAgLyAxMCwgeSA9IEdMTV9lcnJvcl90aHJlc2hvbGRzKQ0KYGBgDQpMb2dpc3RpYyBSZWdyZXNzaW9uIGhhcyBhbiBlcnJvciByYXRlIG9mIDExLjclLCBtYWtpbmcgaXQgdGhlIHdvcnN0IG1vZGVsIHlldC4gSXRzIGVycm9yIGlzIGJlc3Qgd2hlbiB0aHJlc2hvbGQgPSAuNS4NCg0KIyMgMy4yKSBNb2RlbHMgdXNpbmcgc2NhbGVkIGRhdGE6DQoNCg0KVGhlIG1vZGVscyBiZWxvdyBhbGwgcmVxdWlyZSBub3JtYWxpemF0aW9uIG9mIHRoZSBkYXRhIHRvIGJlICBlZmZlY3RpdmUuIFRoaXMgaXMgYW4gaW1wb3J0YW50IHN0ZXAgYXMgYWxsIGZlYXR1cmVzIG5lZWQgdG8gYmUgaW4gdGhlIHNhbWUgc2NhbGUuIElmIG5vdCwgdGhlIGZlYXR1cmVzIHdpdGggbGFyZ2VyIHNjYWxlcyB3b3VsZCBkb21pbmF0ZSB0aGUgbW9kZWwgY2F1c2luZyBpdCB0byBiZSBpbmFjY3VyYXRlLiBUbyBkbyB0aGlzLCB3ZSB1c2VkIHRoZSDigJxzY2FsZeKAnSBmdW5jdGlvbiB0byBub3JtYWxpemUgYWxsIHRoZSBkZXBlbmRlbnQgdmFyaWFibGVzLiBXZSBhbHNvIGNoYW5nZWQgdGhlIGluZGVwZW5kZW50IHZhcmlhYmxlLCBrb2lfZGlzcG9zaXRpb24sIHRvIGJlIGJpbmFyeQ0KDQojIyMgMy4yLjIpIE5ldXJhbCBOZXR3b3JrOiBPcmlnaW5hbCBWYXJpYWJsZXMNCg0KVGhlIE5ldXJhbCBOZXR3b3JrIG1vZGVsIGlzIGJ1aWx0IHRocm91Z2ggZnVuY3Rpb25zLCDigJxuZXVyb25z4oCdIHRoYXQgYXJlIHRoZW4gb3JnYW5pemVkIGludG8gbGF5ZXJzLiBJdCBpcyBhbiBhZHZhbmNlZCBtb2RlbCB0aGF0IGlzIGlkZWFsbHkgc3VpdGVkIGZvciBjb21wbGV4IHByb2JsZW1zIGFzIGl0IHJlcXVpcmVzIHNpZ25pZmljYW50IGNvbXB1dGF0aW9uYWwgcmVzb3VyY2VzLiBJbiBhZGRpdGlvbiwgaXQgaXMgcXVpdGUgZGlmZmljdWx0IHRvIHVuZGVyc3RhbmQgYWZ0ZXJ3YXJkcyBnaXZlbiB0aGUgY29tcGxleGl0eSBvZiB0aGUgbWF0aCB3aXRoaW4gdGhlIG1vZGVsLiBPdXIgZ3JvdXAgd2FzIGFibGUgdG8gc2VlIHRoZSBzaWduaWZpY2FudCB1c2Ugb2YgY29tcHV0YXRpb25hbCByZXNvdXJjZXMgYXMgb3VyIGNvbXB1dGVyIHdhcyB1bmFibGUgdG8gcnVuIHRoZSBtb2RlbC4gRm9yIHRoaXMgcmVhc29uLCB0aGUgZ3JvdXAgZGlkIG5vdCB1c2UgSy1mb2xkIGNyb3NzLXZhbGlkYXRpb24gaW4gb3JkZXIgdG8gbG93ZXIgdGhlIGNvbXB1dGF0aW9uYWwgcG93ZXIgcmVxdWlyZWQgdG8gcnVuIHRoZSBtb2RlbCwgYnV0IGluIHJlYWwtbGlmZSBhcHBsaWNhdGlvbiBLLWZvbGQgY3Jvc3MtdmFsaWRhdGlvbiBzaG91bGQgc3RpbGwgYmUgZG9uZS4gDQoNCldoZW4gY2hvb3NpbmcgdGhlIG51bWJlciBvZiBuZXVyb25zIGFuZCBoaWRkZW4gbGF5ZXJzIGl0IGlzIGltcG9ydGFudCB0byBmaW5kIHRoZSByaWdodCBiYWxhbmNlIGJldHdlZW4gYWNjdXJhY3kgYW5kIG92ZXItZml0dGluZy4gT3VyIGdyb3VwIG1hbnVhbGx5IGFkanVzdGVkIHRoZSBudW1iZXIgb2YgbmV1cm9ucyBhbmQgaGlkZGVuIGxheWVycywgdGVzdGluZyB2YXJpYXRpb25zIHN1Y2ggYXMgNCYyLCA1JjEsIDYsIDMmMSwgZXRjLCB1bnRpbCB3ZSBmb3VuZCB0aGF0IHR3byBuZXVyb25zIGFuZCBvbmUgaGlkZGVuIGxheWVyIGFsbG93ZWQgdGhlIG1vZGVsIHRvIGNvbnZlcmdlLiBUaGUgbmV4dCBzdGVwIGluIHRoZSBtb2RlbCB3YXMgdG8gY2hvb3NlIHRoZSB0aHJlc2hvbGQgdmFsdWUgZm9yIGNsYXNzaWZpY2F0aW9uLiBUaGlzIHdhcyBzaW1pbGFyIHRvIGNob29zaW5nIHRoZSB0aHJlc2hvbGQgYXMgd2UgZGlkIGluIExvZ2lzdGljIFJlZ3Jlc3Npb24uIFRoZSBpZGVhbCB0aHJlc2hvbGQgb2YgMC41IHdhcyBmb3VuZCBieSBwbG90dGluZyB0aGUgYXZlcmFnZSBlcnJvciBmb3IgZWFjaCB0aHJlc2hvbGQgcmFuZ2luZyBmcm9tIDAuMSB0byAxIGFzIHRoYXQgY29ycmVzcG9uZGVkIHdpdGggdGhlIGxvd2VzdCBtaXNjbGFzc2lmaWNhdGlvbiByYXRlLg0KDQoNCmBgYHtyfQ0KI0NyZWF0ZSB0ZXN0aW5nIGFuZCB0cmFpbmluZyBzZXQNCnNldC5zZWVkKDEyMykNCnNjYWxlZF9kYXRhID0gZGF0YS5mcmFtZShzY2FsZShsYWJlbGVkX2ZpbmFsWywgMjozNl0pKSAjbm9ybWFsaXplIGRhdGENCnNjYWxlZF9kYXRhJGtvaV9kaXNwb3NpdGlvbiA9IGlmZWxzZShsYWJlbGVkX2ZpbmFsJGtvaV9kaXNwb3NpdGlvbiA9PSAiQ09ORklSTUVEIiwgMSwgMCkNCm51bV9zYW1wbGVzID0gZGltKHNjYWxlZF9kYXRhKVsxXQ0Kc2FtcGxpbmcucmF0ZSA9IDAuOA0KdHJhaW5pbmcgPSBzYW1wbGUoMTpudW1fc2FtcGxlcywgc2FtcGxpbmcucmF0ZSAqIG51bV9zYW1wbGVzLCByZXBsYWNlID0gRkFMU0UpDQp0cmFpbmluZ1NldC5ub3JtID0gc3Vic2V0KHNjYWxlZF9kYXRhW3RyYWluaW5nLF0pDQp0ZXN0aW5nID0gc2V0ZGlmZigxOm51bV9zYW1wbGVzLCB0cmFpbmluZykNCnRlc3RpbmdTZXQubm9ybSA9IHN1YnNldChzY2FsZWRfZGF0YVt0ZXN0aW5nLCBdKQ0Kc2l6ZVRlc3RTZXQgPSBkaW0odGVzdGluZ1NldC5ub3JtKVsxXQ0KDQojc2V0IHVwIHZhcmlhYmxlcyBmb3IgbmV1cmFsIG5ldHdvcmsuIFNpbmNlIDM2IHZhcmlhYmxlcyBwdXQgdG9vIG11Y2ggY29tcHV0YXRpb25hbCBzdHJhaW4sIGRpbWVuc2lvbmFsaXR5IHJlZHVjdGlvbiBuZWVkZWQgdG8gdGFrZSBwbGFjZS4gV2UgY2hvc2UgdG8gdGFrZSBvbmx5IHRoZSBmZWF0dXJlcywgbm90IHRoZWlyIGVycm9ycywgdG8gcmVkdWNlIGRpbWVuc2lvbmFsaXR5Lg0KDQprb2lEUC5uYW1lID0gImtvaV9kaXNwb3NpdGlvbiINCm51bUNvbHMgPSBkaW0odGVzdGluZ1NldC5ub3JtKVsyXQ0KdmFyaWFibGUubmFtZXMgPSBjb2xuYW1lcyh0ZXN0aW5nU2V0Lm5vcm0pWzE6bnVtQ29scyAtIDFdDQp2YXJpYWJsZS5uYW1lcw0KZm9yIChpIGluIDE6bGVuZ3RoKHZhcmlhYmxlLm5hbWVzKSkgew0KICBlcnJvciA9IGdyZXBsKCJfZXJyIiwgdmFyaWFibGUubmFtZXNbaV0sIGZpeGVkID0gVFJVRSkNCiAgaWYgKGVycm9yKSB7DQogICAgdmFyaWFibGUubmFtZXNbaV0gPSBOQQ0KICB9DQogIA0KfQ0KdmFyaWFibGUubmFtZXMgPSBuYS5vbWl0KHZhcmlhYmxlLm5hbWVzKQ0KdmFyaWFibGUubmFtZXMgPSB2YXJpYWJsZS5uYW1lc1sxOjE1XQ0KI3VzZSBmb3JtdWxhaWMgbGlicmFyeSB0byBjcmVhdGUgZm9ybXVsYQ0Kbm4uZm9ybSA8LQ0KICBjcmVhdGUuZm9ybXVsYShvdXRjb21lLm5hbWUgPSBrb2lEUC5uYW1lLA0KICAgICAgICAgICAgICAgICBpbnB1dC5uYW1lcyA9IHZhcmlhYmxlLm5hbWVzKQ0Kbm4uZm9ybSAjMTUgdmFyaWFibGVzDQoNCg0KI0ZpdCBuZXVyYWwgbmV0d29yaw0Kbm5Nb2RlbDEgPSBuZXVyYWxuZXQoDQogIG5uLmZvcm0sDQogIGRhdGEgPSB0cmFpbmluZ1NldC5ub3JtLA0KICBoaWRkZW4gPSAyLA0KICBsaW5lYXIub3V0cHV0ID0gRkFMU0UsDQogIGFjdC5mY3QgPSAibG9naXN0aWMiLA0KKQ0KcGxvdChubk1vZGVsMSkNCg0KI1ByZWRpY3QNCnByZWRpY3RlZExhYmVscyA9IGNvbXB1dGUobm5Nb2RlbDEsIHRlc3RpbmdTZXQubm9ybVssIHZhcmlhYmxlLm5hbWVzXSkNCg0KI3R1bmUgdGhyZXNob2xkIHBhcmFtZXRlcg0KdGhyZXNob2xkID0gLjENCk5ORXJyb3JzID0gQ3JlYXRlRXJyb3JNYXRyaXgoMTApDQpOTl9GUHMgPSBDcmVhdGVFcnJvck1hdHJpeCgxMCkNCk5OX0ZOcyA9IENyZWF0ZUVycm9yTWF0cml4KDEwKQ0KaW5kZXggPSAxDQp3aGlsZSAodGhyZXNob2xkIDw9IDEpIHsNCiAgcmVzdWx0cyA9IGRhdGEuZnJhbWUoYWN0dWFsID0gdGVzdGluZ1NldC5ub3JtJGtvaV9kaXNwb3NpdGlvbiwNCiAgICAgICAgICAgICAgICAgICAgICAgcHJlZGljdGlvbiA9IHByZWRpY3RlZExhYmVscyRuZXQucmVzdWx0KQ0KICByZXN1bHRzJHJvdW5kZWRQcmVkaWN0aW9uID0gaWZlbHNlKHJlc3VsdHMkcHJlZGljdGlvbiA+IHRocmVzaG9sZCwgMSwgMCkNCiAgI2Vycm9yID0gc3VtKHJlc3VsdHMkYWN0dWFsICE9IHJlc3VsdHMkcm91bmRlZFByZWRpY3Rpb24pDQogIEVycm9ycyA9IEFsbEVycm9ycyhyZXN1bHRzJGFjdHVhbCxyZXN1bHRzJHJvdW5kZWRQcmVkaWN0aW9uLHNpemVUZXN0U2V0LDEpDQogIE5ORXJyb3JzW2luZGV4XSA9IEVycm9yc1tbMV1dDQogIE5OX0ZQc1tpbmRleF0gPSBFcnJvcnNbWzJdXQ0KICBOTl9GTnNbaW5kZXhdID0gRXJyb3JzW1szXV0NCiAgdGhyZXNob2xkID0gdGhyZXNob2xkICsgLjENCiAgaW5kZXggPSBpbmRleCArIDENCn0NCg0Kb3JkZXIoTk5FcnJvcnMpICNsb3dlc3QgbWlzY2xhc3MgcmF0ZSBpcyBhdCB0aHJlc2hvbGQgPSAuNQ0KTmV1cmFsTmV0TWlzQ2xhc3NSYXRlID0gTk5FcnJvcnNbb3JkZXIoTk5FcnJvcnMpWzFdXQ0KQXZnRlBfTk4gPSBOTl9GUHNbb3JkZXIoTk5FcnJvcnMpWzFdXQ0KQXZnRk5fTk4gPSBOTl9GTnNbb3JkZXIoTk5FcnJvcnMpWzFdXQ0KcGFzdGUoIk5ldXJhbCBOZXQgTWlzY2xhc3NpZmljYXRpb24gUmF0ZTogIixyb3VuZCgxMDAqTmV1cmFsTmV0TWlzQ2xhc3NSYXRlLDIpLCIlIixzZXA9IiIpDQpwbG90KA0KICB4ID0gMToxMCAvIDEwLA0KICB5ID0gTk5FcnJvcnMsDQogIG1haW4gPSAiQXZnIEVycm9yIG92ZXIgZGlmZmVyZW50IHRocmVzaG9sZHMgZm9yIE5ldXJhbCBOZXR3b3JrIiwNCiAgeGxhYiA9ICJDdXRvZmYgVGhyZXNob2xkIiwNCiAgeWxhYiA9ICJNaXNjbGFzc2lmaWNhdGlvbiBSYXRlIg0KKQ0KbGluZXMoeCA9IDE6MTAgLyAxMCwgeSA9IE5ORXJyb3JzKQ0KYGBgDQpJbiB0aGlzIHJ1biwgTk4gaGFkIGFuIGVycm9yIHJhdGUgb2YgYHIgTmV1cmFsTmV0TWlzQ2xhc3NSYXRlYCwgd2l0aCBhbiBvcHRpbWFsIGN1dG9mZiB0aHJlc2hvbGQgb2YgLjUuIFdlIHdpbGwgcmV2aXNpdCB0aGUgTk4gYmVsb3csIHRvIGxvb2sgYXQgd2F5cyB0byB1c2UgUENBIHRvIGNhcHR1cmUgdmFyaWF0aW9uIHdoaWxlIHJlZHVjaW5nIG5lY2Vzc2FyeSBjb21wdXRhdGlvbmFsIHJlc291cmNlcy4gDQoNCiMjIyBOZXVyYWwgTmV0d29yazogUENBIHcvMTUgVmFyaWFibGVzDQoNClByaW5jaXBhbCBDb21wb25lbnQgQW5hbHlzaXMgYWxsb3dzIHVzIHRvIHJlZHVjZSBkaW1lbnNpb25hbGl0eSB3aGlsZSBjYXB0dXJpbmcgYXMgbXVjaCBvZiB0aGUgdW5kZXJseWluZyB2YXJpYXRpb24gaW4gdGhlIGRhdGEgYXMgcG9zc2libGUuIFRoZSBmaXJzdCBhcHBsaWNhdGlvbiBvZiBQQ0Egd2UgZm91bmQgd2FzIGZvciBOZXVyYWwgTmV0d29ya3MsIHdoaWNoIGFyZSB2ZXJ5IGNvbXB1dGF0aW9uYWxseSBkaWZmaWN1bHQuIFdlIGRlY2lkZWQgdG8gZmlyc3QgdXNlIGEgUENBIHdpdGggMTUgdmFyaWFibGVzLCB0aGUgc2FtZSBudW1iZXIgb2YgdmFyaWFibGVzIGFzIG91ciBvcmlnaW5hbCBuZXVyYWwgbmV0d29yay4gVGhpcyB3b3VsZCBtZWFuIHRoZSBzYW1lIGNvbXB1dGF0aW9uYWwgc3RyYWluLCBidXQgd2l0aCBmZWF0dXJlcyB0aGF0IGFyZSBndWFyYW50ZWVkIHRvIGNhcHR1cmUgYXMgbXVjaCB2YXJpYXRpb24gYXMgcG9zc2libGUgd2l0aCB0aGF0IG51bWJlciBvZiB2YXJpYWJsZXMuIA0KICAgDQpgYGB7cn0NCnJlcy5wY2EuZXhvcGxhbmV0cyA9IHByY29tcChpZGVudGlmaWVyc19yZW1vdmVkWzI6MzZdLCBjZW50ZXIgPSBUUlVFLCBzY2FsZSA9IFRSVUUpICNwZXJmb3JtIFBDQQ0Kc3VtbWFyeShyZXMucGNhLmV4b3BsYW5ldHMpDQpQb1YgPC0NCiAgcmVzLnBjYS5leG9wbGFuZXRzJHNkZXYgXiAyIC8gc3VtKHJlcy5wY2EuZXhvcGxhbmV0cyRzZGV2IF4gMikgI2dldCBwcm9wb3J0aW9ucyBvZiB2YXJpYW5jZQ0KbnVtUGNhcyA9IDE1DQpzdW0oUG9WWzE6bnVtUGNhc10pICNUaGlzIGdpdmVzIHVzIDg4JSBleHBsYW5hdGlvbiBvZiB2YXJpYW5jZS4gVGhpcyBpcyB0aGUgbnVtYmVyIG9mIHZhcmlhYmxlIGluIHRoZSBvcmlnaW5hbCBuZXVyYWwgbmV0d29yaywgc28gd2UgYXJlIHVzaW5nIHRoaXMgdG8gZ2V0IGEgYmV0dGVyIE5OIHdpdGggdGhlIHNhbWUgbnVtYmVyIG9mIHZhcmlhYmxlcw0KbmV3RGF0YVNldCA9IGRhdGEuZnJhbWUocmVzLnBjYS5leG9wbGFuZXRzJHhbLCAxOm51bVBjYXNdKQ0KbmV3RGF0YVNldCRsYWJlbCA9IGlkZW50aWZpZXJzX3JlbW92ZWQka29pX2Rpc3Bvc2l0aW9uDQpmdml6X3BjYV92YXIocmVzLnBjYS5leG9wbGFuZXRzLCBjb2wudmFyID0gImNvbnRyaWIiKQ0KY2FuZGlkYXRlc19QQ0EgPSBuZXdEYXRhU2V0W2lkZW50aWZpZXJzX3JlbW92ZWQka29pX2Rpc3Bvc2l0aW9uID09DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQ0FORElEQVRFIiwgXSAjc2VwYXJhdGUgb3V0IGp1c3QgdGhlIGNhbmRpZGF0ZXMNCmxhYmVsZWRfUENBID0gbmV3RGF0YVNldFtpZGVudGlmaWVyc19yZW1vdmVkJGtvaV9kaXNwb3NpdGlvbiAhPQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIkNBTkRJREFURSIsIF0NCmxhYmVsZWRfUENBID0gZHJvcGxldmVscyhsYWJlbGVkX1BDQSkNCmNhbmRpZGF0ZXNfUENBID0gZHJvcGxldmVscyhjYW5kaWRhdGVzX1BDQSkNCmBgYA0KUmVydW5uaW5nIE5ldXJhbCBOZXR3b3JrIE1vZGVsDQpgYGB7cn0NCnNldC5zZWVkKDEyMykNCk5OX2xhYmVsZWRfUENBID0gbGFiZWxlZF9QQ0FbLCAxOm51bVBjYXNdDQpOTl9sYWJlbGVkX1BDQSRrb2lfZGlzcG9zaXRpb24gPSBpZmVsc2UobGFiZWxlZF9QQ0EkbGFiZWwgPT0gIkNPTkZJUk1FRCIsIDEsIDApDQpzdW1tYXJ5KHNjYWxlZF9kYXRhKQ0KaGVhZChzY2FsZWRfZGF0YSkNCm51bV9zYW1wbGVzID0gZGltKHNjYWxlZF9kYXRhKVsxXQ0Kc2FtcGxpbmcucmF0ZSA9IDAuOA0KDQojY3JlYXRlIHRyYWluaW5nL3Rlc3Rpbmcgc2V0cw0KdHJhaW5pbmcgPSBzYW1wbGUoMTpudW1fc2FtcGxlcywgc2FtcGxpbmcucmF0ZSAqIG51bV9zYW1wbGVzLCByZXBsYWNlID0gRkFMU0UpDQp0cmFpbmluZ1NldC5ub3JtLlBDQSA9IHN1YnNldChOTl9sYWJlbGVkX1BDQVt0cmFpbmluZyxdKQ0KdGVzdGluZyA9IHNldGRpZmYoMTpudW1fc2FtcGxlcywgdHJhaW5pbmcpDQp0ZXN0aW5nU2V0Lm5vcm0uUENBID0gc3Vic2V0KE5OX2xhYmVsZWRfUENBW3Rlc3RpbmcsIF0pDQpzaXplVGVzdFNldCA9IGRpbSh0ZXN0aW5nU2V0Lm5vcm0pWzFdDQpsYWJlbC5uYW1lID0gImxhYmVsIg0KdmFyaWFibGUubmFtZXMgPSByZXAoMCwgbnVtUGNhcykNCm51bUNvbHMgPSBkaW0odGVzdGluZ1NldC5ub3JtLlBDQSlbMl0NCnZhcmlhYmxlLm5hbWVzID0gY29sbmFtZXModGVzdGluZ1NldC5ub3JtLlBDQSlbMTpudW1Db2xzIC0gMV0NCnZhcmlhYmxlLm5hbWVzDQpubi5mb3JtIDwtDQogIGNyZWF0ZS5mb3JtdWxhKG91dGNvbWUubmFtZSA9IGtvaURQLm5hbWUsDQogICAgICAgICAgICAgICAgIGlucHV0Lm5hbWVzID0gdmFyaWFibGUubmFtZXMpDQpubi5mb3JtDQoNCiNyZXJ1biBuZXVyYWwgbmV0d29yaw0Kbm5Nb2RlbDIgPSBuZXVyYWxuZXQoDQogIG5uLmZvcm0sDQogIGRhdGEgPSB0cmFpbmluZ1NldC5ub3JtLlBDQSwNCiAgaGlkZGVuID0gMiwNCiAgbGluZWFyLm91dHB1dCA9IEZBTFNFLA0KICBhY3QuZmN0ID0gImxvZ2lzdGljIg0KKQ0KcGxvdChubk1vZGVsMikNCiNGaW5kIHJlc3VsdHMNCnByZWRpY3RlZExhYmVscyA9IGNvbXB1dGUobm5Nb2RlbDIsIHRlc3RpbmdTZXQubm9ybS5QQ0FbLCB2YXJpYWJsZS5uYW1lc10pDQoNCiNUdW5lIHRocmVzaG9sZA0KdGhyZXNob2xkID0gLjENCk5ORXJyb3JzID0gcmVwKDAsIDEwKQ0KTk5fRk5zID0gcmVwKDAsMTApDQpOTl9GUHMgPSByZXAoMCwxMCkNCmluZGV4ID0gMQ0Kd2hpbGUgKHRocmVzaG9sZCA8PSAxKSB7DQogIHJlc3VsdHMgPSBkYXRhLmZyYW1lKGFjdHVhbCA9IHRlc3RpbmdTZXQubm9ybS5QQ0Eka29pX2Rpc3Bvc2l0aW9uLA0KICAgICAgICAgICAgICAgICAgICAgICBwcmVkaWN0aW9uID0gcHJlZGljdGVkTGFiZWxzJG5ldC5yZXN1bHQpDQogIHJlc3VsdHMkcm91bmRlZFByZWRpY3Rpb24gPSBpZmVsc2UocmVzdWx0cyRwcmVkaWN0aW9uID4gdGhyZXNob2xkLCAxLCAwKQ0KICBlcnJvciA9IHN1bShyZXN1bHRzJGFjdHVhbCAhPSByZXN1bHRzJHJvdW5kZWRQcmVkaWN0aW9uKQ0KICBFcnJvcnMgPSBBbGxFcnJvcnMocmVzdWx0cyRhY3R1YWwscmVzdWx0cyRyb3VuZGVkUHJlZGljdGlvbixzaXplVGVzdFNldCwxKQ0KICBOTkVycm9yc1tpbmRleF0gPSBFcnJvcnNbWzFdXQ0KICBOTl9GUHNbaW5kZXhdID0gRXJyb3JzW1syXV0NCiAgTk5fRk5zW2luZGV4XSA9IEVycm9yc1tbM11dDQogDQogIHRocmVzaG9sZCA9IHRocmVzaG9sZCArIC4xDQogIGluZGV4ID0gaW5kZXggKyAxDQp9DQpvcmRlcihOTkVycm9ycykgI2xvd2VzdCBtaXNjbGFzc2lmaWNhdGlvbiByYXRlIGlzIGF0IHRocmVzaG9sZCA9IC40DQpQQ0FfMTVfdl9OZXVyYWxOZXRNaXNDbGFzc1JhdGUgPSBOTkVycm9yc1tvcmRlcihOTkVycm9ycylbMV1dDQpBdmdfRlBfUENBMTVOTiA9IE5OX0ZQc1tvcmRlcihOTkVycm9ycylbMV1dDQpBdmdfRk5fUENBMTVOTiA9IE5OX0ZOc1tvcmRlcihOTkVycm9ycylbMV1dDQpwYXN0ZSgiTmV1cmFsIE5ldCAoUENBLCAxNSB2YXIpIE1pc2NsYXNzaWZpY2F0aW9uIFJhdGU6ICIscm91bmQoMTAwKlBDQV8xNV92X05ldXJhbE5ldE1pc0NsYXNzUmF0ZSwyKSwiJSIsc2VwPSIiKQ0KDQpwbG90KA0KICB4ID0gMToxMCAvIDEwLA0KICB5ID0gTk5FcnJvcnMsDQogIG1haW4gPSAiQXZnIEVycm9yIG92ZXIgdGhyZXNob2xkcyBmb3IgUENBIE5ldXJhbCBOZXR3b3JrIHcvMTMgVmFycyIsDQogIHhsYWIgPSAiQ3V0b2ZmIFRocmVzaG9sZCIsDQogIHlsYWIgPSAiTWlzY2xhc3NpZmljYXRpb24gUmF0ZSINCikNCmxpbmVzKHggPSAxOjEwIC8gMTAsIHkgPSBOTkVycm9ycykNCmBgYA0KV2UgZ2V0IGFiZXR0ZXIgbWlzY2xhc3NpZmljYXRpb24gcmF0ZSBmcm9tIHRoZSBQQ0EgdmVyc2lvbiBvZiBOTiB1c2luZyAxMyB2YXJpYWJsZXMgb2YgYHIgcm91bmQoUENBXzE1X3ZfTmV1cmFsTmV0TWlzQ2xhc3NSYXRlKjEwMCwyKWAlLCB3aGljaCBpcyBgciByb3VuZCgxMDAqKE5ldXJhbE5ldE1pc0NsYXNzUmF0ZSAtIFBDQV8xNV92X05ldXJhbE5ldE1pc0NsYXNzUmF0ZSksMilgJSBsZXNzIHRoYW4gb3VyIGxhc3QgTmV1cmFsIE5ldHdvcmssIGFuZCBhbiBvcHRpbWFsIGN1dG9mZiB0aHJlc2hvbGQgb2YgLjUuIA0KDQojIyMgMy4yLjMpIE5ldXJhbCBOZXR3b3JrOiBQQ0Egdy8yMCBWYXJpYWJsZXMNCg0KTGFzdGx5IChmb3IgTk5zKSwgd2UgZGVjaWRlZCB0byBzZWUgaG93IG1hbnkgdmFyaWFibGVzIGNvdWxkIGNhcHR1cmUgOTUlIG9mIHZhcmlhdGlvbi4gV2UgZm91bmQgdGhhdCAyMCB2YXJpYWJsZXMgd2FzIHN1ZmZpY2llbnQ7IHRoaXMgbWVhbnMgdGhhdCAxNiBvZiBvdXIgMzYgdmFyaWFibGVzLCBvciBgciByb3VuZCgxNi8zNioxMDAsMilgJSByZXByZXNlbnQgb25seSA1JSBvZiB2YXJpYXRpb24uIA0KYGBge3J9DQojTGFzdCBOZXVyYWwgTmV0d29yaywgUENBLCB3aXRoIDk1JSBvZiB2YXJpYXRpb24gZXhwbGFpbmVkDQpudW1WYXJzID0gKGRpbSgobGFiZWxlZF9maW5hbCkpWzJdKQ0KZm9yIChpIGluIDE6KG51bVZhcnMpKSB7DQogIGlmIChzdW0oUG9WWzE6aV0pID49IC45NSkgew0KICAgIG51bVBjYXMgPSBpDQogICAgYnJlYWsNCiAgICANCiAgfQ0KfQ0KDQpzdW0oUG9WWzE6bnVtUGNhc10pDQpuZXdEYXRhU2V0ID0gZGF0YS5mcmFtZShyZXMucGNhLmV4b3BsYW5ldHMkeFssIDE6bnVtUGNhc10pDQoNCm5ld0RhdGFTZXQkbGFiZWwgPSBpZGVudGlmaWVyc19yZW1vdmVkJGtvaV9kaXNwb3NpdGlvbg0KDQpjYW5kaWRhdGVzX1BDQSA9IG5ld0RhdGFTZXRbaWRlbnRpZmllcnNfcmVtb3ZlZCRrb2lfZGlzcG9zaXRpb24gPT0NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJDQU5ESURBVEUiLF0gI3NlcGFyYXRlIG91dCBqdXN0IHRoZSBjYW5kaWRhdGVzDQpsYWJlbGVkX1BDQSA9IG5ld0RhdGFTZXRbaWRlbnRpZmllcnNfcmVtb3ZlZCRrb2lfZGlzcG9zaXRpb24gIT0NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICJDQU5ESURBVEUiLF0NCmxhYmVsZWRfUENBID0gZHJvcGxldmVscyhsYWJlbGVkX1BDQSkNCmNhbmRpZGF0ZXNfUENBID0gZHJvcGxldmVscyhjYW5kaWRhdGVzX1BDQSkNCmBgYA0KDQpgYGB7cn0NCnNldC5zZWVkKDEyMykNCk5OX2xhYmVsZWRfUENBID0gbGFiZWxlZF9QQ0FbLCAxOm51bVBjYXNdDQpOTl9sYWJlbGVkX1BDQSRrb2lfZGlzcG9zaXRpb24gPSBpZmVsc2UobGFiZWxlZF9QQ0EkbGFiZWwgPT0gIkNPTkZJUk1FRCIsIDEsIDApDQpoZWFkKHNjYWxlZF9kYXRhKQ0KI3Rlc3RpbmcgYW5kIHRyYWluaW5nIHNldHMNCm51bV9zYW1wbGVzID0gZGltKHNjYWxlZF9kYXRhKVsxXQ0Kc2FtcGxpbmcucmF0ZSA9IDAuOA0KdHJhaW5pbmcgPSBzYW1wbGUoMTpudW1fc2FtcGxlcywgc2FtcGxpbmcucmF0ZSAqIG51bV9zYW1wbGVzLCByZXBsYWNlID0gRkFMU0UpDQp0cmFpbmluZ1NldC5ub3JtLlBDQSA9IHN1YnNldChOTl9sYWJlbGVkX1BDQVt0cmFpbmluZyxdKQ0KdGVzdGluZyA9IHNldGRpZmYoMTpudW1fc2FtcGxlcywgdHJhaW5pbmcpDQp0ZXN0aW5nU2V0Lm5vcm0uUENBID0gc3Vic2V0KE5OX2xhYmVsZWRfUENBW3Rlc3RpbmcsIF0pDQpzaXplVGVzdFNldCA9IGRpbSh0ZXN0aW5nU2V0Lm5vcm0pWzFdDQpsYWJlbC5uYW1lID0gImxhYmVsIg0KdmFyaWFibGUubmFtZXMgPSByZXAoMCwgbnVtUGNhcykNCg0KbnVtQ29scyA9IGRpbSh0ZXN0aW5nU2V0Lm5vcm0uUENBKVsyXQ0KdmFyaWFibGUubmFtZXMgPSBjb2xuYW1lcyh0ZXN0aW5nU2V0Lm5vcm0uUENBKVsxOm51bUNvbHMgLSAxXQ0KDQpubi5mb3JtIDwtDQogIGNyZWF0ZS5mb3JtdWxhKG91dGNvbWUubmFtZSA9IGtvaURQLm5hbWUsDQogICAgICAgICAgICAgICAgIGlucHV0Lm5hbWVzID0gdmFyaWFibGUubmFtZXMpDQpubi5mb3JtDQoNCmxpYnJhcnkobmV1cmFsbmV0KQ0Kbm5Nb2RlbDMgPSBuZXVyYWxuZXQoICNwYXJhbWV0ZXJzIHR1bmVkIG1hbnVhbGx5IHRvIGVuc3VyZSB0aGlzIHN0YXlzIGNvbXB1dGF0aW9uYWxseSB0cmFjdGFibGUNCiAgbm4uZm9ybSwNCiAgZGF0YSA9IHRyYWluaW5nU2V0Lm5vcm0uUENBLA0KICBoaWRkZW4gPSBjKDUpLA0KICBsaW5lYXIub3V0cHV0ID0gRkFMU0UsDQogIGFjdC5mY3QgPSAibG9naXN0aWMiLA0KICBzdGVwbWF4PTE3MDAwLA0KICB0aHJlc2hvbGQgPSAwLjENCikNCg0KcGxvdChubk1vZGVsMykNCg0KI01ha2UgcHJlZGljdGlvbg0KcHJlZGljdGVkTGFiZWxzID0gY29tcHV0ZShubk1vZGVsMywgdGVzdGluZ1NldC5ub3JtLlBDQVssIHZhcmlhYmxlLm5hbWVzXSkNCg0KaW5kZXggPSAxDQp0aHJlc2hvbGQgPSAuMQ0KTk5FcnJvcnMgPSByZXAoMCwgMTApDQpOTl9GUHMgPSByZXAoMCwxMCkNCk5OX0ZOcyA9IHJlcCgwLDEwKQ0Kd2hpbGUgKHRocmVzaG9sZCA8PSAxKSB7ICN0dW5lIHRocmVzaG9sZCBwYXJhbWV0ZXINCiAgcmVzdWx0cyA9IGRhdGEuZnJhbWUoYWN0dWFsID0gdGVzdGluZ1NldC5ub3JtLlBDQSRrb2lfZGlzcG9zaXRpb24sDQogICAgICAgICAgICAgICAgICAgICAgIHByZWRpY3Rpb24gPSBwcmVkaWN0ZWRMYWJlbHMkbmV0LnJlc3VsdCkNCiAgcmVzdWx0cyRyb3VuZGVkUHJlZGljdGlvbiA9IGlmZWxzZShyZXN1bHRzJHByZWRpY3Rpb24gPiB0aHJlc2hvbGQsIDEsIDApDQogIGVycm9yID0gc3VtKHJlc3VsdHMkYWN0dWFsICE9IHJlc3VsdHMkcm91bmRlZFByZWRpY3Rpb24pDQogICBFcnJvcnMgPSBBbGxFcnJvcnMocmVzdWx0cyRhY3R1YWwscmVzdWx0cyRyb3VuZGVkUHJlZGljdGlvbixzaXplVGVzdFNldCwxKQ0KICBOTkVycm9yc1tpbmRleF0gPSBFcnJvcnNbWzFdXQ0KICBOTl9GUHNbaW5kZXhdID0gRXJyb3JzW1syXV0NCiAgTk5fRk5zW2luZGV4XSA9IEVycm9yc1tbM11dDQogIHRocmVzaG9sZCA9IHRocmVzaG9sZCArIC4xDQogIGluZGV4ID0gaW5kZXggKyAxDQp9DQpOTkVycm9ycyAjbG93ZXN0IGVycm9yIGlzIHdpdGggdGhyZXNob2xkID0gLjYNClBDQV8yMF92X05ldXJhbE5ldE1pc0NsYXNzUmF0ZSA9IE5ORXJyb3JzW29yZGVyKE5ORXJyb3JzKVsxXV0NCkF2Z19GUF9QQ0EyME5OID0gTk5fRlBzW29yZGVyKE5ORXJyb3JzKVsxXV0NCkF2Z19GTl9QQ0EyME5OID0gTk5fRk5zW29yZGVyKE5ORXJyb3JzKVsxXV0NCnBsb3QoDQogIHggPSAxOjEwIC8gMTAsDQogIHkgPSBOTkVycm9ycywNCiAgbWFpbiA9ICJBdmcgRXJyb3Igb3ZlciB0aHJlc2hvbGRzIGZvciBQQ0EgTmV1cmFsIE5ldHdvcmsgdy8yMCBWYXJzIiwNCiAgeGxhYiA9ICJDdXRvZmYgVGhyZXNob2xkIiwNCiAgeWxhYiA9ICJNaXNjbGFzc2lmaWNhdGlvbiBSYXRlIg0KKQ0KbGluZXMoeCA9IDE6MTAgLyAxMCwgeSA9IE5ORXJyb3JzKQ0KYGBgDQpBcyBleHBlY3RlZCwgdGhpcyB2ZXJzaW9uIGhhcyB0aGUgbG93ZXN0IG1pc2NsYXNzaWZpY2F0aW9uIHJhdGUgb2YgYHIgcm91bmQoUENBXzIwX3ZfTmV1cmFsTmV0TWlzQ2xhc3NSYXRlKjEwMCwyKWAlLCB3aGljaCBpcyBgciByb3VuZCgoUENBXzE1X3ZfTmV1cmFsTmV0TWlzQ2xhc3NSYXRlLVBDQV8yMF92X05ldXJhbE5ldE1pc0NsYXNzUmF0ZSkqMTAwLDIpYCUgbG93ZXIgdGhhbiB0aGUgMTMgdmFyaWFibGUgUENBIE5OLCBhbmQgYHIgcm91bmQoKE5ldXJhbE5ldE1pc0NsYXNzUmF0ZS1QQ0FfMjBfdl9OZXVyYWxOZXRNaXNDbGFzc1JhdGUpKjEwMCwyKWAlIGxvd2VyIHRoYW4gdGhlIG9yaWdpbmFsIG5ldXJhbCBuZXR3b3JrLiBBcyB5b3UgY2FuIHNlZSwgaG93ZXZlciwgd2UgYXJlIHJlYWNoaW5nIGEgcG9pbnQgb2YgZGltaW5pc2hpbmcgcmV0dXJuczsgYWRkaW5nIH4xNSUgbW9yZSB2YXJpYXRpb24gb25seSBkZWNyZWFzZWQgdGhlIGVycm9yIHJhdGUgYnkgDQpgciByb3VuZCgoUENBXzE1X3ZfTmV1cmFsTmV0TWlzQ2xhc3NSYXRlLVBDQV8yMF92X05ldXJhbE5ldE1pc0NsYXNzUmF0ZSkqMTAwLDIpYCUNCiAgICANCiMjIyAzLjIuNCkgSy1OZWFyZXN0IE5laWdoYm91cnMNCiAgDQprTk4gd29ya3MgYnkgY29tcHV0aW5nIHRoZSBldWNsaWRlYW4gZGlzdGFuY2VzIG9mIHRoZSB0ZXN0IGZlYXR1cmVzIHRvIHRyYWluaW5nIGRhdGEgcG9pbnRzLCBrbm93biBhcyDigJxuZWlnaGJvdXJz4oCdLiBUaGlzIG1vZGVsIHJlcXVpcmVzIHByZS1wcm9jZXNzaW5nIGJlY2F1c2UgdGhlIGRpc3RhbmNlcyBmcm9tIGVhY2ggZGF0YSBwb2ludCBtdXN0IGJlIGluIHRoZSBzYW1lIHNjYWxlLCB0aGVyZWZvcmUgd2UgZmlyc3Qgbm9ybWFsaXplZCB0aGUgdHJhaW5pbmcgYW5kIHRlc3RpbmcgZmVhdHVyZXMuIFRoaXMgbW9kZWwgaGFzIGFuIGlucHV0IHBhcmFtZXRlciwgaywgd2hpY2ggcmVwcmVzZW50cyB0aGUgbnVtYmVyIG9mIG5laWdoYm91cnMgY29uc2lkZXJlZC4gQm90aCB0b28tc21hbGwgYW5kIHRvby1sYXJnZSB2YWx1ZXMgb2YgayBjYW4gYmUgZGV0cmltZW50YWwuIFRvIHR1bmUgdGhpcyBwYXJhbWV0ZXIsIHdlIHRlc3RlZCBzZXZlcmFsIHZhbHVlcyBvZiBrIGluIGEgbG9vcCwgc2VsZWN0aW5nIHRoZSBrIHdoaWNoIHJlc3VsdGVkIGluIHRoZSBsb3dlc3QgbWlzY2xhc3NpZmljYXRpb24gcmF0ZS4NCg0KYGBge3J9DQpudW1LcyA9IDEwDQprX2Vycm9ycyA9IHJlcCgwLCBudW1LcykNCmtfRlBzID0gcmVwKDAsbnVtS3MpDQprX0ZOcyA9IHJlcCgwLG51bUtzKQ0KZm9yIChraSBpbiAxOm51bUtzKSB7DQogICN0dW5lIGsgcGFyYW1ldGVyDQogIGF2Z0Vycm9yc19mb2xkID0gQ3JlYXRlRXJyb3JNYXRyaXgoTnVtRm9sZHMpDQogIGF2Z0ZQX2ZvbGQgPSBDcmVhdGVFcnJvck1hdHJpeChOdW1Gb2xkcykNCiAgYXZnRk5fZm9sZCA9IENyZWF0ZUVycm9yTWF0cml4KE51bUZvbGRzKQ0KICBmb3IgKGZvbGQgaW4gMTpOdW1Gb2xkcykgew0KICAgICNrLWZvbGQgY3Jvc3MgdmFsaWRhdGlvbg0KICAgIHNldC5zZWVkKGZvbGQpDQogICAgI21ha2Ugbm9ybWFsaXplZCB0cmFpbmluZyBhbmQgdGVzdGluZyBzZXRzDQogICAgc2NhbGVkX2RhdGEgPSBkYXRhLmZyYW1lKHNjYWxlKGxhYmVsZWRfZmluYWxbLCAyOjM2XSkpDQogICAgc2NhbGVkX2RhdGEka29pX2Rpc3Bvc2l0aW9uID0gaWZlbHNlKGxhYmVsZWRfZmluYWwka29pX2Rpc3Bvc2l0aW9uID09ICJDT05GSVJNRUQiLCAxLCAwKQ0KICAgIHN1bW1hcnkoc2NhbGVkX2RhdGEpDQogICAgaGVhZChzY2FsZWRfZGF0YSkNCiAgICBudW1fc2FtcGxlcyA9IGRpbShzY2FsZWRfZGF0YSlbMV0NCiAgICBzYW1wbGluZy5yYXRlID0gMC44DQogICAgdHJhaW5pbmcgPSBzYW1wbGUoMTpudW1fc2FtcGxlcywgc2FtcGxpbmcucmF0ZSAqIG51bV9zYW1wbGVzLCByZXBsYWNlID0gRkFMU0UpDQogICAgdHJhaW5pbmdTZXQubm9ybSA9IHN1YnNldChzY2FsZWRfZGF0YVt0cmFpbmluZyxdKQ0KICAgIHRlc3RpbmcgPSBzZXRkaWZmKDE6bnVtX3NhbXBsZXMsIHRyYWluaW5nKQ0KICAgIHRlc3RpbmdTZXQubm9ybSA9IHN1YnNldChzY2FsZWRfZGF0YVt0ZXN0aW5nLCBdKQ0KICAgIHNpemVUZXN0U2V0ID0gZGltKHRlc3RpbmdTZXQubm9ybSlbMV0NCiAgICANCiAgICB0cmFpbmluZ2ZlYXR1cmVzID0gc3Vic2V0KHRyYWluaW5nU2V0Lm5vcm0sIHNlbGVjdCA9IGMoLWtvaV9kaXNwb3NpdGlvbikpDQogICAgdHJhaW5pbmdsYWJlbHMgPSB0cmFpbmluZ1NldC5ub3JtJGtvaV9kaXNwb3NpdGlvbg0KICAgIHRlc3RpbmdmZWF0dXJlcyA9IHN1YnNldCh0ZXN0aW5nU2V0Lm5vcm0sIHNlbGVjdCA9IGMoLWtvaV9kaXNwb3NpdGlvbikpDQogICAgdGVzdGluZ2xhYmVscyA9IHRlc3RpbmdTZXQubm9ybSRrb2lfZGlzcG9zaXRpb24NCiAgICANCiAgICANCiAgICAjZml0IG1vZGVsIGFuZCBwcmVkaWN0DQogICAgcHJlZGljdGVkTGFiZWxzID0ga25uKHRyYWluaW5nZmVhdHVyZXMsIHRlc3RpbmdmZWF0dXJlcywgdHJhaW5pbmdsYWJlbHMsIGsgPQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGtpKQ0KICAgICNkZXRlcm1pbmUgZXJyb3INCiAgICBlcnJvciA9IHN1bShwcmVkaWN0ZWRMYWJlbHMgIT0gdGVzdGluZ1NldC5ub3JtJGtvaV9kaXNwb3NpdGlvbikNCiAgICBFcnJvcnMgPSBBbGxFcnJvcnModGVzdGluZ1NldC5ub3JtJGtvaV9kaXNwb3NpdGlvbiwNCiAgICAgICAgICAgICAgICAgICAgICAgcHJlZGljdGVkTGFiZWxzLA0KICAgICAgICAgICAgICAgICAgICAgICBzaXplVGVzdFNldCwNCiAgICAgICAgICAgICAgICAgICAgICAgMSkNCiAgICBtaXNjbGFzc2lmaWNhdGlvbl9yYXRlID0gZXJyb3IgLyBzaXplVGVzdFNldA0KICAgIGF2Z0Vycm9yc19mb2xkW2ZvbGRdID0gRXJyb3JzW1sxXV0NCiAgICBhdmdGUF9mb2xkW2ZvbGRdID0gRXJyb3JzW1syXV0NCiAgICBhdmdGTl9mb2xkW2ZvbGRdID0gRXJyb3JzW1szXV0NCiAgICANCiAgfQ0KICANCiAga19lcnJvcnNba2ldID0gbWVhbihhdmdFcnJvcnNfZm9sZCkNCiAga19GUHNba2ldID0gbWVhbihhdmdGUF9mb2xkKQ0KICBrX0ZOc1traV0gPSBtZWFuKGF2Z0ZOX2ZvbGQpDQp9DQpwcmludChvcmRlcihrX2Vycm9ycykpDQojVGhlIGxvd2VzdCBhdmVyYWdlIGVycm9yICh0aGlzIHJ1bikgaXMgZnJvbSB0aGUgbW9kZWwgd2l0aCBrID0gNC4NCkF2Z0Vycm9yX2Jlc3Rfa25uID0ga19lcnJvcnNbb3JkZXIoa19lcnJvcnMpWzFdXQ0KQXZnRlBfa25uID0ga19GUHNbb3JkZXIoa19lcnJvcnMpWzFdXQ0KQXZnRk5fa25uID0ga19GTnNbb3JkZXIoa19lcnJvcnMpWzFdXQ0KYGBgDQpLTk4gcHJvZHVjZXMgYW4gZXJyb3Igb2YgYHIgcm91bmQoQXZnRXJyb3JfYmVzdF9rbm4qMTAwLDIpYCUsIHdpdGggYW4gb3B0aW1hbCBrLXZhbHVlIG9mIGByIG9yZGVyKGtfZXJyb3JzKVsxXWANCg0KDQojIyMgMy4yLjUpIEstTWVhbnMgQ2x1c3RlcmluZw0KQ2x1c3RlcmluZyBpcyBhbiB1bnN1cGVydmlzZWQgbW9kZWwgd2hpY2ggdXNlcyByYW5kb21seSBnZW5lcmF0ZWQgY2VudHJvaWRzIGFuZCBhc3NpZ25zIGV2ZXJ5IHBvaW50IHRvIGEgY2VudHJvaWQgYmFzZWQgb24gZXVjbGlkZWFuIGRpc3RhbmNlLiBUaGUgbW9kZWwgdGhlbiBpdGVyYXRlcyB0byBmaW5kIHRoZSBjZW50cm9pZCBsb2NhdGlvbnMgd2hpY2ggbWluaW1pemUgdGhlIGRpc3RhbmNlcyB0byB0aGUgZGF0YSBwb2ludHMgaW4gdGhlIGNsdXN0ZXJzLiBTaW5jZSB0aGlzIG1vZGVsIGFsc28gcmVxdWlyZXMgY2FsY3VsYXRpb24gb2YgZXVjbGlkZWFuIGRpc3RhbmNlLCB0aGUgbm9ybWFsaXplZCBkYXRhIHNldCB3YXMgYWxzbyB1c2VkIGhlcmUuIA0KDQpTaW5jZSBvdXIgZGF0YSBzZXQgd2FzIGxhYmVsbGVkLCB0aGUgc3VwZXJ2aXNlZCBtb2RlbHMgd2lsbCBsaWtlbHkgeWllbGQgYSBiZXR0ZXIgbWlzY2xhc3NpZmljYXRpb24gcmF0ZSB0aGFuIGstTWVhbnMgY2x1c3RlcmluZy4gV2UgaW5jbHVkZSBjbHVzdGVyaW5nIGluIGNhc2UgdGhlIGFsZ29yaXRobSBmb3VuZCB1bmZvcmVzZWVuIHJlbGF0aW9uc2hpcHMgaW4gdGhlIHVubGFiZWxsZWQgZGF0YSBmZWF0dXJlcy4NCg0KYGBge3J9DQojc2NhbGUgZnVsbCBkYXRhIHNldC4NCnNldC5zZWVkKDEyMykNCnNjYWxlZF9kYXRhID0gZGF0YS5mcmFtZShzY2FsZShsYWJlbGVkX2ZpbmFsWywgMjozNl0pKQ0Kc2NhbGVkX2RhdGEka29pX2Rpc3Bvc2l0aW9uID0gaWZlbHNlKGxhYmVsZWRfZmluYWwka29pX2Rpc3Bvc2l0aW9uID09ICJDT05GSVJNRUQiLCAyLCAxKQ0KbnVtX3NhbXBsZXMgPSBkaW0oc2NhbGVkX2RhdGEpWzFdDQpmZWF0dXJlcyA9IHN1YnNldChzY2FsZWRfZGF0YSwgc2VsZWN0ID0gYygta29pX2Rpc3Bvc2l0aW9uKSkNCiNmaXQgdGhlIG1vZGVsDQprY2x1c3RlcmluZyA9IGttZWFucyhmZWF0dXJlcywgY2VudGVycyA9IDIsIG5zdGFydCA9IDI1KQ0KI3Zpc3VhbGl6ZSB0aGUgY2x1c3RlcnMNCmZ2aXpfY2x1c3RlcihrY2x1c3RlcmluZywgZGF0YSA9IGZlYXR1cmVzKQ0KZXJyb3IgPSBzdW0oc2NhbGVkX2RhdGEka29pX2Rpc3Bvc2l0aW9uICE9IGtjbHVzdGVyaW5nJGNsdXN0ZXIpDQptaXNjbGFzc2lmaWNhdGlvbl9yYXRlID0gZXJyb3IgLyBkaW0oc2NhbGVkX2RhdGEpWzFdDQoNCg0KaXNXcm9uZyA9IChzY2FsZWRfZGF0YSRrb2lfZGlzcG9zaXRpb24gIT0ga2NsdXN0ZXJpbmckY2x1c3RlcikNCmlzUmlnaHQgPSAoc2NhbGVkX2RhdGEka29pX2Rpc3Bvc2l0aW9uID09IGtjbHVzdGVyaW5nJGNsdXN0ZXIpDQpJc0MgPSAoa2NsdXN0ZXJpbmckY2x1c3RlciA9PSAyKQ0KSXNGID0gKGtjbHVzdGVyaW5nJGNsdXN0ZXIgPT0gMSkNCkZhbHNlUG9zaXRpdmVzID0gc3VtKGlzV3JvbmcgJiBJc0MpDQpGYWxzZU5lZ2F0aXZlcyA9IHN1bShpc1dyb25nICYgSXNGKQ0KVHJ1ZVBvc2l0aXZlcyA9IHN1bShpc1JpZ2h0ICYgSXNDKQ0KVHJ1ZU5lZ2F0aXZlcyA9IHN1bShpc1JpZ2h0ICYgSXNGKQ0KQ2x1c3RlcmluZ19GUF9SYXRlID0gKEZhbHNlUG9zaXRpdmVzIC8gKEZhbHNlUG9zaXRpdmVzICsgVHJ1ZU5lZ2F0aXZlcykpI2RlZmluZSBGUCByYXRlIGFzIEZQLyhGUCtUTikNCkNsdXN0ZXJpbmdfRk5fUmF0ZSA9IChGYWxzZU5lZ2F0aXZlcyAvIChGYWxzZU5lZ2F0aXZlcyArIFRydWVQb3NpdGl2ZXMpKSNkZWZpbmUgRk4gcmF0ZSBhcyBGTi8oRk4rVFApDQoNCg0KQXZnRXJyb3JDbHVzdGVyaW5nID0gbWlzY2xhc3NpZmljYXRpb25fcmF0ZQ0KaWYgKEF2Z0Vycm9yQ2x1c3RlcmluZyA+IC41KSB7DQogIEF2Z0Vycm9yQ2x1c3RlcmluZyA9IDEgLSBBdmdFcnJvckNsdXN0ZXJpbmcjc2luY2UgY2x1c3RlcmluZyBkb2VzIG5vdCBrbm93IHdoaWNoIGNsdXN0ZXIgaXMgQ09ORklSTUVEIGFuZCB3aGljaCBpcyBGQUxTRSBQT1NJVElWRSwgdGhleSBjYW4gYmUgZmxpcHBlZA0KICBDbHVzdGVyaW5nX0ZQX1JhdGUgPSAxIC0gQ2x1c3RlcmluZ19GUF9SYXRlDQogIENsdXN0ZXJpbmdfRk5fUmF0ZSA9IDEgLSBDbHVzdGVyaW5nX0ZOX1JhdGUNCiAgDQp9DQoNCmBgYA0KVW5zdXJwcmlzaW5nbHksIGNsdXN0ZXJpbmcgaGFzIHRoZSBsYXJnZXN0IGVycm9yLCBvZiBgciByb3VuZChBdmdFcnJvckNsdXN0ZXJpbmcqMTAwLDIpYCU7IGdpdmVuIHRoaXMgaXMgYW4gdW5zdXBlcnZpc2VkIG1ldGhvZCBhbmQgb3VyIGRhdGEgaGFzIGxhYmVscy4gQWRkaXRpb25hbGx5LCBDbHVzdGVyaW5nIGlzIGVzcGVjaWFsbHkgdGVycmlibGUgYXQgcHJlZGljdGluZyAiRkFMU0UgUE9TSVRJVkVzLCIgd2l0aCBhIGZhbHNlIG5lZ2F0aXZlIHJhdGUgb2YgYHIgcm91bmQoQ2x1c3RlcmluZ19GTl9SYXRlKjEwMCwyKWAlDQoNCiMgNCkgU2VsZWN0IEJlc3QgTW9kZWwNCg0KYGBge3J9DQplcnJvcl9vdXRwdXQgPSBkYXRhLmZyYW1lKA0KICAiTW9kZWwiID0gYygNCiAgICAiRGVjaXNpb24gVHJlZSIsDQogICAgIkdMTSIsDQogICAgIlJhbmRvbSBGb3Jlc3QiLA0KICAgICJTVk0iLA0KICAgICJOZXVyYWwgTmV0IiwNCiAgICAiS05OIiwNCiAgICAiQ2x1c3RlcmluZyIsDQogICAgIlBDQSAxNSBWYXIgTk4iLA0KICAgICJQQ0EgMjAgVmFyIE5OIiwNCiAgICAiWEdCb29zdCINCiAgKSwNCiAgIk1pc2NsYXNzaWZpY2F0aW9uIFJhdGUiID0gYygNCiAgICBBdmdFcnJvckRULA0KICAgIEF2Z0Vycm9yR0xNLA0KICAgIEF2Z0Vycm9yUkYsDQogICAgQXZnRXJyb3JTVk0sDQogICAgTmV1cmFsTmV0TWlzQ2xhc3NSYXRlLA0KICAgIEF2Z0Vycm9yX2Jlc3Rfa25uLA0KICAgIEF2Z0Vycm9yQ2x1c3RlcmluZywNCiAgICBQQ0FfMTVfdl9OZXVyYWxOZXRNaXNDbGFzc1JhdGUsDQogICAgUENBXzIwX3ZfTmV1cmFsTmV0TWlzQ2xhc3NSYXRlLA0KICAgIEF2Z0Vycm9yWEdCDQogICksICJGYWxzZSBQb3NpdGl2ZSBSYXRlIiA9IGMoDQogICAgQXZnRlBfRFQsDQogICAgQXZnRlBfR0xNLA0KICAgIEF2Z0ZQX1JGLA0KICAgIEF2Z0ZQX1NWTSwNCiAgICBBdmdGUF9OTiwNCiAgICBBdmdGUF9rbm4sDQogICAgQ2x1c3RlcmluZ19GUF9SYXRlLA0KICAgIEF2Z19GUF9QQ0ExNU5OLA0KICAgIEF2Z19GUF9QQ0EyME5OLA0KICAgIEF2Z0ZQX1hHQg0KICApLCAiRmFsc2UgTmVnYXRpdmUgUmF0ZSIgPSBjKA0KICAgIEF2Z0ZOX0RULA0KICAgIEF2Z0ZOX0dMTSwNCiAgICBBdmdGTl9SRiwNCiAgICBBdmdGTl9TVk0sDQogICAgQXZnRk5fTk4sDQogICAgQXZnRk5fa25uLA0KICAgIENsdXN0ZXJpbmdfRk5fUmF0ZSwNCiAgICBBdmdfRk5fUENBMTVOTiwNCiAgICBBdmdfRk5fUENBMjBOTiwNCiAgICBBdmdGTl9YR0INCiAgKQ0KKQ0KcHJpbnQoZXJyb3Jfb3V0cHV0KQ0KYGBgDQpYR0Jvb3N0IGhhcyB0aGUgbG93ZXN0IG1pc2NsYXNzaWZpY2F0aW9uIHJhdGU7IGFsdGhvdWdoIGl0IGhhcyBhIHNsaWdodGx5IGhpZ2hlciBGYWxzZSBQb3NpdGl2ZSByYXRlIHRoYW4gc29tZSBvdGhlciBtb2RkbGVzLCBpdCBzdGlsbCBpbGx1c3RyYXRlcyB0aGUgYmVzdCBvdmVyYWxsIGFjY3VyYWN5LiBXZSB3aWxsIHJlbWFrZSB0aGlzIG1vZGVsIHVzaW5nIHRoZSBmdWxsIGRhdGFzZXQuDQoNCg0KIzUgTWFrZSBwcmVkaWN0aW9ucw0KDQpSZW1ha2UgdGhlIFhHQm9vc3QgbW9kZWwgdXNpbmcgdGhlIGZ1bGwgZGF0YXNldDoNCmBgYHtyfQ0KICAgeGdEYXRhID0gZGF0YS5tYXRyaXgobGFiZWxlZF9maW5hbCkNCiAgICB4Z0RhdGFbLCAxXSA9IGlmZWxzZSh4Z0RhdGFbLCAxXSA9PSAyLCAxLCAwKQ0KICAgDQogICAgeGdCb29zdE1vZGVsRmluYWwgPSB4Z2Jvb3N0KA0KICAgICAgZGF0YSA9IHhnRGF0YVssIDI6MzZdLA0KICAgICAgbGFiZWwgPSB4Z0RhdGFbLCAxXSwNCiAgICAgIG1heC5kZXB0aCA9IDYsDQogICAgICBldGEgPSAuMjIsDQogICAgICBucm91bmRzID0gMTAwLA0KICAgICAgdmVyYm9zZSA9IDAsDQogICAgICBvYmplY3RpdmUgPSAiYmluYXJ5OmxvZ2lzdGljIiwNCiAgICAgIGV2YWxfbWV0cmljPSJlcnJvciINCiAgICApDQogICAgeGdQcmVkaWN0ID0gZGF0YS5tYXRyaXgoY2FuZGlkYXRlc19maW5hbCkNCiAgICB4Z1ByZWRpY3RbLCAxXSA9IGlmZWxzZSh4Z1ByZWRpY3RbLCAxXSA9PSAyLCAxLCAwKQ0KICAgIA0KICAgDQpgYGANClByZWRpY3QgbGFiZWxzIG9mIGNhbmRpZGF0ZXMgZGF0YXNldCwgYW5kIHdyaXRlIGl0IHRvIGZpbGUNCmBgYHtyfQ0KICNtYWtlIHByZWRpY3Rpb25zDQpCb29zdFByZWRpY3Rpb25zID0gcHJlZGljdCh4Z0Jvb3N0TW9kZWxGaW5hbCwgZGF0YS5tYXRyaXgoY2FuZGlkYXRlc19maW5hbClbLCAyOjM2XSkNCkJvb3N0UHJlZGljdGlvbnNSb3VuZGVkID0gaWZlbHNlKEJvb3N0UHJlZGljdGlvbnMgPiAuNCwgMSwgMCkNCnByZWRpY3RlZExhYmVscyA9IGlmZWxzZShCb29zdFByZWRpY3Rpb25zUm91bmRlZCA9PSAxLCAiRkFMU0UgUE9TSVRJVkUiLCAiQ09ORklSTUVEIikNCmNhbmRpZGF0ZXNfZmluYWwka29pX2Rpc3Bvc2l0aW9uID0gcHJlZGljdGVkTGFiZWxzDQpoZWFkKGNhbmRpZGF0ZXNfZmluYWwpDQp3cml0ZS5jc3YoY2FuZGlkYXRlc19maW5hbCwgImxhYmVsZWRDYW5kaWRhdGVzLmNzdiIpDQpgYGANCg0KIA0KIA==